Merge "Docs: Fix the doc strings for the prepareFrom* methods." into oc-support-26.0-dev
diff --git a/.gitignore b/.gitignore
index e57740b..abfef7c 100644
--- a/.gitignore
+++ b/.gitignore
@@ -14,3 +14,5 @@
 *.iml
 **/out
 buildSrc/build
+lifecycle/common/build
+jacoco.exec
\ No newline at end of file
diff --git a/annotations/src/android/support/annotation/MainThread.java b/annotations/src/android/support/annotation/MainThread.java
index 5478751..2f50306 100644
--- a/annotations/src/android/support/annotation/MainThread.java
+++ b/annotations/src/android/support/annotation/MainThread.java
@@ -34,9 +34,17 @@
  *  @MainThread
  *  public void deliverResult(D data) { ... }
  * </code></pre>
+ *
+ * <p class="note"><b>Note:</b> Ordinarily, an app's main thread is also the UI
+ * thread. However, However, under special circumstances, an app's main thread
+ * might not be its UI thread; for more information, see
+ * <a href="/studio/write/annotations.html#thread-annotations">Thread
+ * annotations</a>.
+ *
+ * @see android.support.annotation.UiThread
  */
 @Documented
 @Retention(CLASS)
 @Target({METHOD,CONSTRUCTOR,TYPE})
 public @interface MainThread {
-}
\ No newline at end of file
+}
diff --git a/annotations/src/android/support/annotation/UiThread.java b/annotations/src/android/support/annotation/UiThread.java
index ef81986..0a9a0c1 100644
--- a/annotations/src/android/support/annotation/UiThread.java
+++ b/annotations/src/android/support/annotation/UiThread.java
@@ -35,9 +35,17 @@
  *
  *  public abstract void setText(@NonNull String text) { ... }
  * </code></pre>
+ *
+ * <p class="note"><b>Note:</b> Ordinarily, an app's UI thread is also the main
+ * thread. However, However, under special circumstances, an app's UI thread
+ * might not be its main thread; for more information, see
+ * <a href="/studio/write/annotations.html#thread-annotations">Thread
+ * annotations</a>.
+ *
+ * @see android.support.annotation.MainThread
  */
 @Documented
 @Retention(CLASS)
 @Target({METHOD,CONSTRUCTOR,TYPE})
 public @interface UiThread {
-}
\ No newline at end of file
+}
diff --git a/api/26.0.0-SNAPSHOT.txt b/api/26.0.0-SNAPSHOT.txt
index 2cd0c13..e022198 100644
--- a/api/26.0.0-SNAPSHOT.txt
+++ b/api/26.0.0-SNAPSHOT.txt
@@ -50,6 +50,9 @@
     ctor public FlingAnimation(K, android.support.animation.FloatPropertyCompat<K>);
     method public float getFriction();
     method public android.support.animation.FlingAnimation setFriction(float);
+    method public android.support.animation.FlingAnimation setMaxValue(float);
+    method public android.support.animation.FlingAnimation setMinValue(float);
+    method public android.support.animation.FlingAnimation setStartVelocity(float);
   }
 
   public abstract class FloatPropertyCompat<T> {
@@ -378,6 +381,9 @@
     ctor public AppBarLayout(android.content.Context);
     ctor public AppBarLayout(android.content.Context, android.util.AttributeSet);
     method public void addOnOffsetChangedListener(android.support.design.widget.AppBarLayout.OnOffsetChangedListener);
+    method protected android.support.design.widget.AppBarLayout.LayoutParams generateDefaultLayoutParams();
+    method public android.support.design.widget.AppBarLayout.LayoutParams generateLayoutParams(android.util.AttributeSet);
+    method protected android.support.design.widget.AppBarLayout.LayoutParams generateLayoutParams(android.view.ViewGroup.LayoutParams);
     method public deprecated float getTargetElevation();
     method public final int getTotalScrollRange();
     method public void removeOnOffsetChangedListener(android.support.design.widget.AppBarLayout.OnOffsetChangedListener);
@@ -510,6 +516,9 @@
     method public boolean getSkipCollapsed();
     method public final int getState();
     method public boolean isHideable();
+    method public void onNestedPreScroll(android.support.design.widget.CoordinatorLayout, V, android.view.View, int, int, int[]);
+    method public boolean onStartNestedScroll(android.support.design.widget.CoordinatorLayout, V, android.view.View, android.view.View, int);
+    method public void onStopNestedScroll(android.support.design.widget.CoordinatorLayout, V, android.view.View);
     method public void setBottomSheetCallback(android.support.design.widget.BottomSheetBehavior.BottomSheetCallback);
     method public void setHideable(boolean);
     method public final void setPeekHeight(int);
@@ -550,6 +559,9 @@
     ctor public CollapsingToolbarLayout(android.content.Context);
     ctor public CollapsingToolbarLayout(android.content.Context, android.util.AttributeSet);
     ctor public CollapsingToolbarLayout(android.content.Context, android.util.AttributeSet, int);
+    method protected android.support.design.widget.CollapsingToolbarLayout.LayoutParams generateDefaultLayoutParams();
+    method public android.widget.FrameLayout.LayoutParams generateLayoutParams(android.util.AttributeSet);
+    method protected android.widget.FrameLayout.LayoutParams generateLayoutParams(android.view.ViewGroup.LayoutParams);
     method public int getCollapsedTitleGravity();
     method public android.graphics.Typeface getCollapsedTitleTypeface();
     method public android.graphics.drawable.Drawable getContentScrim();
@@ -615,6 +627,9 @@
     ctor public CoordinatorLayout(android.content.Context, android.util.AttributeSet, int);
     method public void dispatchDependentViewsChanged(android.view.View);
     method public boolean doViewsOverlap(android.view.View, android.view.View);
+    method protected android.support.design.widget.CoordinatorLayout.LayoutParams generateDefaultLayoutParams();
+    method public android.support.design.widget.CoordinatorLayout.LayoutParams generateLayoutParams(android.util.AttributeSet);
+    method protected android.support.design.widget.CoordinatorLayout.LayoutParams generateLayoutParams(android.view.ViewGroup.LayoutParams);
     method public java.util.List<android.view.View> getDependencies(android.view.View);
     method public java.util.List<android.view.View> getDependents(android.view.View);
     method public android.graphics.drawable.Drawable getStatusBarBackground();
@@ -708,6 +723,7 @@
     method public boolean getUseCompatPadding();
     method public void hide();
     method public void hide(android.support.design.widget.FloatingActionButton.OnVisibilityChangedListener);
+    method public void setBackgroundDrawable(android.graphics.drawable.Drawable);
     method public void setCompatElevation(float);
     method public void setRippleColor(int);
     method public void setSize(int);
@@ -847,6 +863,7 @@
     method public void addTab(android.support.design.widget.TabLayout.Tab, boolean);
     method public void addTab(android.support.design.widget.TabLayout.Tab, int, boolean);
     method public void clearOnTabSelectedListeners();
+    method public android.widget.FrameLayout.LayoutParams generateLayoutParams(android.util.AttributeSet);
     method public int getSelectedTabPosition();
     method public android.support.design.widget.TabLayout.Tab getTabAt(int);
     method public int getTabCount();
@@ -1027,6 +1044,8 @@
   public class ExifInterface {
     ctor public ExifInterface(java.lang.String) throws java.io.IOException;
     ctor public ExifInterface(java.io.InputStream) throws java.io.IOException;
+    method public void flipHorizontally();
+    method public void flipVertically();
     method public double getAltitude(double);
     method public java.lang.String getAttribute(java.lang.String);
     method public double getAttributeDouble(java.lang.String, double);
@@ -1039,6 +1058,8 @@
     method public long[] getThumbnailRange();
     method public boolean hasThumbnail();
     method public boolean isThumbnailCompressed();
+    method public void resetOrientation();
+    method public void rotate(int);
     method public void saveAttributes() throws java.io.IOException;
     method public void setAltitude(double);
     method public void setAttribute(java.lang.String, java.lang.String);
@@ -1269,10 +1290,12 @@
   }
 
   public final class PreviewProgram {
+    method public boolean equals(java.lang.Object);
     method public static android.support.media.tv.PreviewProgram fromCursor(android.database.Cursor);
     method public long getChannelId();
     method public int getWeight();
     method public android.content.ContentValues toContentValues();
+    method public java.lang.String toString();
   }
 
   public static final class PreviewProgram.Builder {
@@ -1285,13 +1308,16 @@
 
   public final class Program implements java.lang.Comparable {
     method public int compareTo(android.support.media.tv.Program);
+    method public boolean equals(java.lang.Object);
     method public static android.support.media.tv.Program fromCursor(android.database.Cursor);
     method public java.lang.String[] getBroadcastGenres();
     method public long getChannelId();
     method public long getEndTimeUtcMillis();
     method public long getStartTimeUtcMillis();
+    method public int hashCode();
     method public boolean isRecordingProhibited();
     method public android.content.ContentValues toContentValues();
+    method public java.lang.String toString();
   }
 
   public static class Program.Builder {
@@ -1697,10 +1723,12 @@
   }
 
   public final class WatchNextProgram {
+    method public boolean equals(java.lang.Object);
     method public static android.support.media.tv.WatchNextProgram fromCursor(android.database.Cursor);
     method public long getLastEngagementTimeUtcMillis();
     method public int getWatchNextType();
     method public android.content.ContentValues toContentValues();
+    method public java.lang.String toString();
   }
 
   public static final class WatchNextProgram.Builder {
@@ -1719,6 +1747,8 @@
     ctor public PercentFrameLayout(android.content.Context);
     ctor public PercentFrameLayout(android.content.Context, android.util.AttributeSet);
     ctor public PercentFrameLayout(android.content.Context, android.util.AttributeSet, int);
+    method protected android.support.percent.PercentFrameLayout.LayoutParams generateDefaultLayoutParams();
+    method public android.support.percent.PercentFrameLayout.LayoutParams generateLayoutParams(android.util.AttributeSet);
   }
 
   public static deprecated class PercentFrameLayout.LayoutParams extends android.widget.FrameLayout.LayoutParams implements android.support.percent.PercentLayoutHelper.PercentLayoutParams {
@@ -1767,6 +1797,8 @@
     ctor public PercentRelativeLayout(android.content.Context);
     ctor public PercentRelativeLayout(android.content.Context, android.util.AttributeSet);
     ctor public PercentRelativeLayout(android.content.Context, android.util.AttributeSet, int);
+    method protected android.support.percent.PercentRelativeLayout.LayoutParams generateDefaultLayoutParams();
+    method public android.support.percent.PercentRelativeLayout.LayoutParams generateLayoutParams(android.util.AttributeSet);
   }
 
   public static deprecated class PercentRelativeLayout.LayoutParams extends android.widget.RelativeLayout.LayoutParams implements android.support.percent.PercentLayoutHelper.PercentLayoutParams {
@@ -1783,6 +1815,7 @@
 
   public class EmojiCompat {
     method public static android.support.text.emoji.EmojiCompat get();
+    method public java.lang.String getAssetSignature();
     method public int getLoadState();
     method public static boolean handleDeleteSurroundingText(android.view.inputmethod.InputConnection, android.text.Editable, int, int, boolean);
     method public static boolean handleOnKeyDown(android.text.Editable, int, android.view.KeyEvent);
@@ -1918,7 +1951,9 @@
     ctor public EmojiExtractTextLayout(android.content.Context, android.util.AttributeSet);
     ctor public EmojiExtractTextLayout(android.content.Context, android.util.AttributeSet, int);
     ctor public EmojiExtractTextLayout(android.content.Context, android.util.AttributeSet, int, int);
+    method public int getEmojiReplaceStrategy();
     method public void onUpdateExtractingViews(android.inputmethodservice.InputMethodService, android.view.inputmethod.EditorInfo);
+    method public void setEmojiReplaceStrategy(int);
   }
 
   public class EmojiTextView extends android.widget.TextView {
@@ -2160,14 +2195,27 @@
   public class TransitionSet extends android.support.transition.Transition {
     ctor public TransitionSet();
     ctor public TransitionSet(android.content.Context, android.util.AttributeSet);
+    method public android.support.transition.TransitionSet addListener(android.support.transition.Transition.TransitionListener);
+    method public android.support.transition.TransitionSet addTarget(android.view.View);
+    method public android.support.transition.TransitionSet addTarget(int);
+    method public android.support.transition.TransitionSet addTarget(java.lang.String);
+    method public android.support.transition.TransitionSet addTarget(java.lang.Class);
     method public android.support.transition.TransitionSet addTransition(android.support.transition.Transition);
     method public void captureEndValues(android.support.transition.TransitionValues);
     method public void captureStartValues(android.support.transition.TransitionValues);
     method public int getOrdering();
     method public android.support.transition.Transition getTransitionAt(int);
     method public int getTransitionCount();
+    method public android.support.transition.TransitionSet removeListener(android.support.transition.Transition.TransitionListener);
+    method public android.support.transition.TransitionSet removeTarget(int);
+    method public android.support.transition.TransitionSet removeTarget(android.view.View);
+    method public android.support.transition.TransitionSet removeTarget(java.lang.Class);
+    method public android.support.transition.TransitionSet removeTarget(java.lang.String);
     method public android.support.transition.TransitionSet removeTransition(android.support.transition.Transition);
+    method public android.support.transition.TransitionSet setDuration(long);
+    method public android.support.transition.TransitionSet setInterpolator(android.animation.TimeInterpolator);
     method public android.support.transition.TransitionSet setOrdering(int);
+    method public android.support.transition.TransitionSet setStartDelay(long);
     field public static final int ORDERING_SEQUENTIAL = 1; // 0x1
     field public static final int ORDERING_TOGETHER = 0; // 0x0
   }
@@ -2242,6 +2290,7 @@
     ctor public FragmentTabHost(android.content.Context, android.util.AttributeSet);
     method public void addTab(android.widget.TabHost.TabSpec, java.lang.Class<?>, android.os.Bundle);
     method public void onTabChanged(java.lang.String);
+    method public deprecated void setup();
     method public void setup(android.content.Context, android.app.FragmentManager);
     method public void setup(android.content.Context, android.app.FragmentManager, int);
   }
@@ -3001,6 +3050,10 @@
     method public long getSupportedActions();
     method public boolean hasValidMedia();
     method public boolean isMediaPlaying();
+    method protected void pausePlayback();
+    method protected void skipToNext();
+    method protected void skipToPrevious();
+    method protected void startPlayback(int);
   }
 
   public abstract class OnboardingFragment extends android.app.Fragment {
@@ -3165,6 +3218,7 @@
 
   public class PlaybackFragmentGlueHost extends android.support.v17.leanback.media.PlaybackGlueHost implements android.support.v17.leanback.widget.PlaybackSeekUi {
     ctor public PlaybackFragmentGlueHost(android.support.v17.leanback.app.PlaybackFragment);
+    method public void fadeOut();
     method public void setPlaybackSeekUiClient(android.support.v17.leanback.widget.PlaybackSeekUi.Client);
   }
 
@@ -3261,6 +3315,7 @@
 
   public class PlaybackSupportFragmentGlueHost extends android.support.v17.leanback.media.PlaybackGlueHost implements android.support.v17.leanback.widget.PlaybackSeekUi {
     ctor public PlaybackSupportFragmentGlueHost(android.support.v17.leanback.app.PlaybackSupportFragment);
+    method public void fadeOut();
     method public void setPlaybackSeekUiClient(android.support.v17.leanback.widget.PlaybackSeekUi.Client);
   }
 
@@ -3921,6 +3976,9 @@
     ctor public BaseCardView(android.content.Context);
     ctor public BaseCardView(android.content.Context, android.util.AttributeSet);
     ctor public BaseCardView(android.content.Context, android.util.AttributeSet, int);
+    method protected android.support.v17.leanback.widget.BaseCardView.LayoutParams generateDefaultLayoutParams();
+    method public android.support.v17.leanback.widget.BaseCardView.LayoutParams generateLayoutParams(android.util.AttributeSet);
+    method protected android.support.v17.leanback.widget.BaseCardView.LayoutParams generateLayoutParams(android.view.ViewGroup.LayoutParams);
     method public int getCardType();
     method public deprecated int getExtraVisibility();
     method public int getInfoVisibility();
@@ -5934,6 +5992,8 @@
     method public android.support.v4.app.FragmentManager getSupportFragmentManager();
     method public android.support.v4.app.LoaderManager getSupportLoaderManager();
     method public void onAttachFragment(android.support.v4.app.Fragment);
+    method public void onMultiWindowModeChanged(boolean);
+    method public void onPictureInPictureModeChanged(boolean);
     method protected void onResumeFragments();
     method public java.lang.Object onRetainCustomNonConfigurationInstance();
     method public final java.lang.Object onRetainNonConfigurationInstance();
@@ -6100,6 +6160,7 @@
     ctor public FragmentTabHost(android.content.Context, android.util.AttributeSet);
     method public void addTab(android.widget.TabHost.TabSpec, java.lang.Class<?>, android.os.Bundle);
     method public void onTabChanged(java.lang.String);
+    method public deprecated void setup();
     method public void setup(android.content.Context, android.support.v4.app.FragmentManager);
     method public void setup(android.content.Context, android.support.v4.app.FragmentManager, int);
   }
@@ -6235,7 +6296,6 @@
     method public static int getActionCount(android.app.Notification);
     method public static int getBadgeIconType(android.app.Notification);
     method public static java.lang.String getCategory(android.app.Notification);
-    method public static deprecated java.lang.String getChannel(android.app.Notification);
     method public static java.lang.String getChannelId(android.app.Notification);
     method public static android.os.Bundle getExtras(android.app.Notification);
     method public static java.lang.String getGroup(android.app.Notification);
@@ -6243,7 +6303,6 @@
     method public static boolean getLocalOnly(android.app.Notification);
     method public static java.lang.String getShortcutId(android.app.Notification);
     method public static java.lang.String getSortKey(android.app.Notification);
-    method public static deprecated long getTimeout(android.app.Notification);
     method public static long getTimeoutAfter(android.app.Notification);
     method public static boolean isGroupSummary(android.app.Notification);
     field public static final int BADGE_ICON_LARGE = 2; // 0x2
@@ -6400,7 +6459,6 @@
     method public android.support.v4.app.NotificationCompat.Builder setAutoCancel(boolean);
     method public android.support.v4.app.NotificationCompat.Builder setBadgeIconType(int);
     method public android.support.v4.app.NotificationCompat.Builder setCategory(java.lang.String);
-    method public deprecated android.support.v4.app.NotificationCompat.Builder setChannel(java.lang.String);
     method public android.support.v4.app.NotificationCompat.Builder setChannelId(java.lang.String);
     method public android.support.v4.app.NotificationCompat.Builder setColor(int);
     method public android.support.v4.app.NotificationCompat.Builder setColorized(boolean);
@@ -6440,7 +6498,6 @@
     method public android.support.v4.app.NotificationCompat.Builder setSubText(java.lang.CharSequence);
     method public android.support.v4.app.NotificationCompat.Builder setTicker(java.lang.CharSequence);
     method public android.support.v4.app.NotificationCompat.Builder setTicker(java.lang.CharSequence, android.widget.RemoteViews);
-    method public deprecated android.support.v4.app.NotificationCompat.Builder setTimeout(long);
     method public android.support.v4.app.NotificationCompat.Builder setTimeoutAfter(long);
     method public android.support.v4.app.NotificationCompat.Builder setUsesChronometer(boolean);
     method public android.support.v4.app.NotificationCompat.Builder setVibrate(long[]);
@@ -7082,6 +7139,7 @@
     method public void setCircular(boolean);
     method public void setColorFilter(android.graphics.ColorFilter);
     method public void setCornerRadius(float);
+    method public void setDither(boolean);
     method public void setGravity(int);
     method public void setMipMap(boolean);
     method public void setTargetDensity(android.graphics.Canvas);
@@ -7544,6 +7602,7 @@
     method public abstract void sendCustomAction(java.lang.String, android.os.Bundle);
     method public abstract void setCaptioningEnabled(boolean);
     method public abstract void setRating(android.support.v4.media.RatingCompat);
+    method public abstract void setRating(android.support.v4.media.RatingCompat, android.os.Bundle);
     method public abstract void setRepeatMode(int);
     method public abstract void setShuffleMode(int);
     method public abstract deprecated void setShuffleModeEnabled(boolean);
@@ -7585,12 +7644,12 @@
     method public void setSessionActivity(android.app.PendingIntent);
     method public void setShuffleMode(int);
     method public deprecated void setShuffleModeEnabled(boolean);
-    field public static final java.lang.String ACTION_ARGUMENT_MEDIA_ATTRIBUTE = "android.support.v4.media.session.action.ARGUMENT_MEDIA_ATTRIBUTE";
-    field public static final java.lang.String ACTION_ARGUMENT_MEDIA_ATTRIBUTE_VALUE = "android.support.v4.media.session.action.ARGUMENT_MEDIA_ATTRIBUTE_VALUE";
     field public static final java.lang.String ACTION_FLAG_AS_INAPPROPRIATE = "android.support.v4.media.session.action.FLAG_AS_INAPPROPRIATE";
     field public static final java.lang.String ACTION_FOLLOW = "android.support.v4.media.session.action.FOLLOW";
     field public static final java.lang.String ACTION_SKIP_AD = "android.support.v4.media.session.action.SKIP_AD";
     field public static final java.lang.String ACTION_UNFOLLOW = "android.support.v4.media.session.action.UNFOLLOW";
+    field public static final java.lang.String ARGUMENT_MEDIA_ATTRIBUTE = "android.support.v4.media.session.ARGUMENT_MEDIA_ATTRIBUTE";
+    field public static final java.lang.String ARGUMENT_MEDIA_ATTRIBUTE_VALUE = "android.support.v4.media.session.ARGUMENT_MEDIA_ATTRIBUTE_VALUE";
     field public static final int FLAG_HANDLES_MEDIA_BUTTONS = 1; // 0x1
     field public static final int FLAG_HANDLES_QUEUE_COMMANDS = 4; // 0x4
     field public static final int FLAG_HANDLES_TRANSPORT_CONTROLS = 2; // 0x2
@@ -7622,6 +7681,7 @@
     method public void onSeekTo(long);
     method public void onSetCaptioningEnabled(boolean);
     method public void onSetRating(android.support.v4.media.RatingCompat);
+    method public void onSetRating(android.support.v4.media.RatingCompat, android.os.Bundle);
     method public void onSetRepeatMode(int);
     method public void onSetShuffleMode(int);
     method public deprecated void onSetShuffleModeEnabled(boolean);
@@ -8193,6 +8253,10 @@
     method public void trimToSize(int);
   }
 
+  public class ObjectsCompat {
+    method public static boolean equals(java.lang.Object, java.lang.Object);
+  }
+
   public class Pair<F, S> {
     ctor public Pair(F, S);
     method public static <A, B> android.support.v4.util.Pair<A, B> create(A, B);
@@ -8269,14 +8333,6 @@
 
 }
 
-package android.support.v4.utils {
-
-  public class ObjectUtils {
-    method public static boolean objectEquals(java.lang.Object, java.lang.Object);
-  }
-
-}
-
 package android.support.v4.view {
 
   public abstract class AbsSavedState implements android.os.Parcelable {
@@ -8621,6 +8677,7 @@
     ctor public PagerTabStrip(android.content.Context, android.util.AttributeSet);
     method public boolean getDrawFullUnderline();
     method public int getTabIndicatorColor();
+    method public void setBackgroundDrawable(android.graphics.drawable.Drawable);
     method public void setDrawFullUnderline(boolean);
     method public void setTabIndicatorColor(int);
     method public void setTabIndicatorColorResource(int);
@@ -9370,6 +9427,7 @@
 
   public class AccessibilityRecordCompat {
     ctor public deprecated AccessibilityRecordCompat(java.lang.Object);
+    method public deprecated boolean equals(java.lang.Object);
     method public deprecated int getAddedCount();
     method public deprecated java.lang.CharSequence getBeforeText();
     method public deprecated java.lang.CharSequence getClassName();
@@ -9390,6 +9448,7 @@
     method public deprecated java.util.List<java.lang.CharSequence> getText();
     method public deprecated int getToIndex();
     method public deprecated int getWindowId();
+    method public deprecated int hashCode();
     method public deprecated boolean isChecked();
     method public deprecated boolean isEnabled();
     method public deprecated boolean isFullScreen();
@@ -10275,6 +10334,7 @@
     method public deprecated void setSupportProgressBarIndeterminateVisibility(boolean);
     method public deprecated void setSupportProgressBarVisibility(boolean);
     method public android.support.v7.view.ActionMode startSupportActionMode(android.support.v7.view.ActionMode.Callback);
+    method public void supportInvalidateOptionsMenu();
     method public void supportNavigateUpTo(android.content.Intent);
     method public boolean supportRequestWindowFeature(int);
     method public boolean supportShouldUpRecreateTask(android.content.Intent);
@@ -10425,8 +10485,8 @@
     method public static android.support.v4.media.session.MediaSessionCompat.Token getMediaSession(android.app.Notification);
   }
 
-  public static class NotificationCompat.Builder extends android.support.v4.app.NotificationCompat.Builder {
-    ctor public NotificationCompat.Builder(android.content.Context);
+  public static deprecated class NotificationCompat.Builder extends android.support.v4.app.NotificationCompat.Builder {
+    ctor public deprecated NotificationCompat.Builder(android.content.Context);
   }
 
   public static class NotificationCompat.DecoratedCustomViewStyle extends android.support.v4.app.NotificationCompat.Style {
@@ -11552,6 +11612,9 @@
     ctor public ActionMenuView(android.content.Context);
     ctor public ActionMenuView(android.content.Context, android.util.AttributeSet);
     method public void dismissPopupMenus();
+    method protected android.support.v7.widget.ActionMenuView.LayoutParams generateDefaultLayoutParams();
+    method public android.support.v7.widget.ActionMenuView.LayoutParams generateLayoutParams(android.util.AttributeSet);
+    method protected android.support.v7.widget.ActionMenuView.LayoutParams generateLayoutParams(android.view.ViewGroup.LayoutParams);
     method public android.view.Menu getMenu();
     method public android.graphics.drawable.Drawable getOverflowIcon();
     method public int getPopupTheme();
@@ -11585,13 +11648,17 @@
     ctor public AppCompatAutoCompleteTextView(android.content.Context);
     ctor public AppCompatAutoCompleteTextView(android.content.Context, android.util.AttributeSet);
     ctor public AppCompatAutoCompleteTextView(android.content.Context, android.util.AttributeSet, int);
+    method public void setBackgroundDrawable(android.graphics.drawable.Drawable);
+    method public void setTextAppearance(android.content.Context, int);
   }
 
   public class AppCompatButton extends android.widget.Button implements android.support.v4.view.TintableBackgroundView {
     ctor public AppCompatButton(android.content.Context);
     ctor public AppCompatButton(android.content.Context, android.util.AttributeSet);
     ctor public AppCompatButton(android.content.Context, android.util.AttributeSet, int);
+    method public void setBackgroundDrawable(android.graphics.drawable.Drawable);
     method public void setSupportAllCaps(boolean);
+    method public void setTextAppearance(android.content.Context, int);
   }
 
   public class AppCompatCheckBox extends android.widget.CheckBox implements android.support.v4.widget.TintableCompoundButton {
@@ -11604,30 +11671,37 @@
     ctor public AppCompatCheckedTextView(android.content.Context);
     ctor public AppCompatCheckedTextView(android.content.Context, android.util.AttributeSet);
     ctor public AppCompatCheckedTextView(android.content.Context, android.util.AttributeSet, int);
+    method public void setTextAppearance(android.content.Context, int);
   }
 
   public class AppCompatEditText extends android.widget.EditText implements android.support.v4.view.TintableBackgroundView {
     ctor public AppCompatEditText(android.content.Context);
     ctor public AppCompatEditText(android.content.Context, android.util.AttributeSet);
     ctor public AppCompatEditText(android.content.Context, android.util.AttributeSet, int);
+    method public void setBackgroundDrawable(android.graphics.drawable.Drawable);
+    method public void setTextAppearance(android.content.Context, int);
   }
 
   public class AppCompatImageButton extends android.widget.ImageButton implements android.support.v4.view.TintableBackgroundView {
     ctor public AppCompatImageButton(android.content.Context);
     ctor public AppCompatImageButton(android.content.Context, android.util.AttributeSet);
     ctor public AppCompatImageButton(android.content.Context, android.util.AttributeSet, int);
+    method public void setBackgroundDrawable(android.graphics.drawable.Drawable);
   }
 
   public class AppCompatImageView extends android.widget.ImageView implements android.support.v4.view.TintableBackgroundView {
     ctor public AppCompatImageView(android.content.Context);
     ctor public AppCompatImageView(android.content.Context, android.util.AttributeSet);
     ctor public AppCompatImageView(android.content.Context, android.util.AttributeSet, int);
+    method public void setBackgroundDrawable(android.graphics.drawable.Drawable);
   }
 
   public class AppCompatMultiAutoCompleteTextView extends android.widget.MultiAutoCompleteTextView implements android.support.v4.view.TintableBackgroundView {
     ctor public AppCompatMultiAutoCompleteTextView(android.content.Context);
     ctor public AppCompatMultiAutoCompleteTextView(android.content.Context, android.util.AttributeSet);
     ctor public AppCompatMultiAutoCompleteTextView(android.content.Context, android.util.AttributeSet, int);
+    method public void setBackgroundDrawable(android.graphics.drawable.Drawable);
+    method public void setTextAppearance(android.content.Context, int);
   }
 
   public class AppCompatRadioButton extends android.widget.RadioButton implements android.support.v4.widget.TintableCompoundButton {
@@ -11655,12 +11729,15 @@
     ctor public AppCompatSpinner(android.content.Context, android.util.AttributeSet, int);
     ctor public AppCompatSpinner(android.content.Context, android.util.AttributeSet, int, int);
     ctor public AppCompatSpinner(android.content.Context, android.util.AttributeSet, int, int, android.content.res.Resources.Theme);
+    method public void setBackgroundDrawable(android.graphics.drawable.Drawable);
   }
 
   public class AppCompatTextView extends android.widget.TextView implements android.support.v4.view.TintableBackgroundView {
     ctor public AppCompatTextView(android.content.Context);
     ctor public AppCompatTextView(android.content.Context, android.util.AttributeSet);
     ctor public AppCompatTextView(android.content.Context, android.util.AttributeSet, int);
+    method public void setBackgroundDrawable(android.graphics.drawable.Drawable);
+    method public void setTextAppearance(android.content.Context, int);
   }
 
   public class CardView extends android.widget.FrameLayout {
@@ -11711,6 +11788,9 @@
     ctor public GridLayout(android.content.Context, android.util.AttributeSet, int);
     ctor public GridLayout(android.content.Context, android.util.AttributeSet);
     ctor public GridLayout(android.content.Context);
+    method protected android.support.v7.widget.GridLayout.LayoutParams generateDefaultLayoutParams();
+    method public android.support.v7.widget.GridLayout.LayoutParams generateLayoutParams(android.util.AttributeSet);
+    method protected android.support.v7.widget.GridLayout.LayoutParams generateLayoutParams(android.view.ViewGroup.LayoutParams);
     method public int getAlignmentMode();
     method public int getColumnCount();
     method public int getOrientation();
@@ -11812,6 +11892,9 @@
     ctor public LinearLayoutCompat(android.content.Context);
     ctor public LinearLayoutCompat(android.content.Context, android.util.AttributeSet);
     ctor public LinearLayoutCompat(android.content.Context, android.util.AttributeSet, int);
+    method protected android.support.v7.widget.LinearLayoutCompat.LayoutParams generateDefaultLayoutParams();
+    method public android.support.v7.widget.LinearLayoutCompat.LayoutParams generateLayoutParams(android.util.AttributeSet);
+    method protected android.support.v7.widget.LinearLayoutCompat.LayoutParams generateLayoutParams(android.view.ViewGroup.LayoutParams);
     method public int getBaselineAlignedChildIndex();
     method public android.graphics.drawable.Drawable getDividerDrawable();
     method public int getDividerPadding();
@@ -12014,6 +12097,7 @@
   public class PagerSnapHelper extends android.support.v7.widget.SnapHelper {
     ctor public PagerSnapHelper();
     method public int[] calculateDistanceToFinalSnap(android.support.v7.widget.RecyclerView.LayoutManager, android.view.View);
+    method protected android.support.v7.widget.LinearSmoothScroller createSnapScroller(android.support.v7.widget.RecyclerView.LayoutManager);
     method public android.view.View findSnapView(android.support.v7.widget.RecyclerView.LayoutManager);
     method public int findTargetSnapPosition(android.support.v7.widget.RecyclerView.LayoutManager, int, int);
   }
@@ -12658,7 +12742,8 @@
     method public void attachToRecyclerView(android.support.v7.widget.RecyclerView) throws java.lang.IllegalStateException;
     method public abstract int[] calculateDistanceToFinalSnap(android.support.v7.widget.RecyclerView.LayoutManager, android.view.View);
     method public int[] calculateScrollDistance(int, int);
-    method protected android.support.v7.widget.LinearSmoothScroller createSnapScroller(android.support.v7.widget.RecyclerView.LayoutManager);
+    method protected android.support.v7.widget.RecyclerView.SmoothScroller createScroller(android.support.v7.widget.RecyclerView.LayoutManager);
+    method protected deprecated android.support.v7.widget.LinearSmoothScroller createSnapScroller(android.support.v7.widget.RecyclerView.LayoutManager);
     method public abstract android.view.View findSnapView(android.support.v7.widget.RecyclerView.LayoutManager);
     method public abstract int findTargetSnapPosition(android.support.v7.widget.RecyclerView.LayoutManager, int, int);
     method public boolean onFling(int, int);
@@ -12758,6 +12843,9 @@
     ctor public Toolbar(android.content.Context, android.util.AttributeSet, int);
     method public void collapseActionView();
     method public void dismissPopupMenus();
+    method protected android.support.v7.widget.Toolbar.LayoutParams generateDefaultLayoutParams();
+    method public android.support.v7.widget.Toolbar.LayoutParams generateLayoutParams(android.util.AttributeSet);
+    method protected android.support.v7.widget.Toolbar.LayoutParams generateLayoutParams(android.view.ViewGroup.LayoutParams);
     method public int getContentInsetEnd();
     method public int getContentInsetEndWithActions();
     method public int getContentInsetLeft();
@@ -12939,6 +13027,7 @@
     ctor public BoxInsetLayout(android.content.Context);
     ctor public BoxInsetLayout(android.content.Context, android.util.AttributeSet);
     ctor public BoxInsetLayout(android.content.Context, android.util.AttributeSet, int);
+    method public android.support.wear.widget.BoxInsetLayout.LayoutParams generateLayoutParams(android.util.AttributeSet);
     method protected void onLayout(boolean, int, int, int, int);
   }
 
@@ -12994,6 +13083,22 @@
     method public void onLayoutFinished(android.view.View, android.support.v7.widget.RecyclerView);
   }
 
+  public class RoundedDrawable extends android.graphics.drawable.Drawable {
+    ctor public RoundedDrawable();
+    method public void draw(android.graphics.Canvas);
+    method public int getBackgroundColor();
+    method public android.graphics.drawable.Drawable getDrawable();
+    method public int getOpacity();
+    method public int getRadius();
+    method public boolean isClipEnabled();
+    method public void setAlpha(int);
+    method public void setBackgroundColor(int);
+    method public void setClipEnabled(boolean);
+    method public void setColorFilter(android.graphics.ColorFilter);
+    method public void setDrawable(android.graphics.drawable.Drawable);
+    method public void setRadius(int);
+  }
+
   public class SwipeDismissFrameLayout extends android.widget.FrameLayout {
     ctor public SwipeDismissFrameLayout(android.content.Context);
     ctor public SwipeDismissFrameLayout(android.content.Context, android.util.AttributeSet);
diff --git a/app-toolkit/.gitignore b/app-toolkit/.gitignore
new file mode 100644
index 0000000..be4e6f1
--- /dev/null
+++ b/app-toolkit/.gitignore
@@ -0,0 +1,4 @@
+local.properties
+maven-repo/
+build/
+*.DS_Store
diff --git a/app-toolkit/README.md b/app-toolkit/README.md
new file mode 100644
index 0000000..e36944f
--- /dev/null
+++ b/app-toolkit/README.md
@@ -0,0 +1,3 @@
+This is a wrapper project for flatfoot projects.
+You can use either individual projects or this one.
+Build server uses this project to build flatfoot.
\ No newline at end of file
diff --git a/app-toolkit/build.gradle b/app-toolkit/build.gradle
new file mode 100644
index 0000000..6a9d5a0
--- /dev/null
+++ b/app-toolkit/build.gradle
@@ -0,0 +1,30 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+buildscript {
+    ext.supportRootFolder = new File(project.projectDir, "../")
+    apply from: 'buildSrc/repos.gradle'
+    apply from: 'init.gradle'
+    repos.addMavenRepositories(repositories)
+    dependencies {
+        classpath libs.jacoco
+        classpath libs.gradle
+        classpath libs.kotlin.gradle_plugin
+        if (enablePublicRepos) {
+            classpath libs.localize_maven
+        }
+    }
+}
diff --git a/app-toolkit/buildSrc b/app-toolkit/buildSrc
new file mode 120000
index 0000000..053a423
--- /dev/null
+++ b/app-toolkit/buildSrc
@@ -0,0 +1 @@
+../buildSrc
\ No newline at end of file
diff --git a/app-toolkit/common/build.gradle b/app-toolkit/common/build.gradle
new file mode 100644
index 0000000..2f54c2a
--- /dev/null
+++ b/app-toolkit/common/build.gradle
@@ -0,0 +1,31 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+apply plugin: 'java'
+apply plugin: 'maven'
+
+sourceCompatibility = 1.7
+
+dependencies {
+    compile libs.support.annotations
+
+    testCompile libs.junit
+    testCompile libs.mockito_core
+}
+
+archivesBaseName = "common"
+
+createAndroidCheckstyle(project)
diff --git a/app-toolkit/common/src/main/java/android/arch/core/internal/SafeIterableMap.java b/app-toolkit/common/src/main/java/android/arch/core/internal/SafeIterableMap.java
new file mode 100644
index 0000000..63ac9a6
--- /dev/null
+++ b/app-toolkit/common/src/main/java/android/arch/core/internal/SafeIterableMap.java
@@ -0,0 +1,278 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.arch.core.internal;
+
+import android.support.annotation.NonNull;
+import android.support.annotation.RestrictTo;
+
+import java.util.Iterator;
+import java.util.Map;
+import java.util.WeakHashMap;
+
+/**
+ * A map class that can keep a list associated pairs of keys and values
+ * and allows modifications while traversing.
+ * It is NOT thread safe.
+ *
+ * @param <K> Key type
+ * @param <V> Value type
+ * @hide
+ */
+@RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
+@SuppressWarnings("WeakerAccess")
+public class SafeIterableMap<K, V> implements Iterable<Map.Entry<K, V>> {
+
+    private Entry<K, V> mStart;
+    private Entry<K, V> mEnd;
+    // using WeakHashMap over List<WeakReference>, so we don't have to manually remove
+    // WeakReferences that have null in them.
+    private WeakHashMap<ListIterator<K, V>, Boolean> mIterators = new WeakHashMap<>();
+    private int mSize = 0;
+    @SuppressWarnings("unchecked")
+    private static final Entry UNREACHABLE = new Entry(new Object(), new Object());
+
+    private Entry<K, V> find(K k) {
+        Entry<K, V> toRemove = mStart;
+        while (toRemove != null) {
+            if (toRemove.mKey.equals(k)) {
+                break;
+            }
+            toRemove = toRemove.mNext;
+        }
+        return toRemove;
+    }
+
+    /**
+     * If the specified key is not already associated
+     * with a value, associates it with the given value.
+     *
+     * @param key key with which the specified value is to be associated
+     * @param v   value to be associated with the specified key
+     * @return the previous value associated with the specified key,
+     * or {@code null} if there was no mapping for the key
+     */
+    public V putIfAbsent(@NonNull K key, @NonNull V v) {
+        Entry<K, V> entry = find(key);
+        if (entry != null) {
+            return entry.mValue;
+        }
+        Entry<K, V> newEntry = new Entry<>(key, v);
+        mSize++;
+        if (mEnd == null) {
+            mStart = newEntry;
+            mEnd = mStart;
+            return null;
+        }
+
+        mEnd.mNext = newEntry;
+        newEntry.mPrevious = mEnd;
+        mEnd = newEntry;
+        return null;
+    }
+
+    /**
+     * Removes the mapping for a key from this map if it is present.
+     *
+     * @param key key whose mapping is to be removed from the map
+     * @return the previous value associated with the specified key,
+     * or {@code null} if there was no mapping for the key
+     */
+    public V remove(@NonNull K key) {
+        Entry<K, V> toRemove = find(key);
+        if (toRemove == null) {
+            return null;
+        }
+        mSize--;
+        if (!mIterators.isEmpty()) {
+            for (ListIterator<K, V> iter : mIterators.keySet()) {
+                iter.supportRemove(toRemove);
+            }
+        }
+
+        if (toRemove.mPrevious != null) {
+            toRemove.mPrevious.mNext = toRemove.mNext;
+        } else {
+            mStart = toRemove.mNext;
+        }
+
+        if (toRemove.mNext != null) {
+            toRemove.mNext.mPrevious = toRemove.mPrevious;
+        } else {
+            mEnd = toRemove.mPrevious;
+        }
+
+        toRemove.mNext = null;
+        toRemove.mPrevious = null;
+        return toRemove.mValue;
+    }
+
+    /**
+     * @return the number of elements in this map
+     */
+    public int size() {
+        return mSize;
+    }
+
+    @Override
+    public ListIterator<K, V> iterator() {
+        ListIterator<K, V> iterator = new ListIterator<>(mStart, mEnd);
+        mIterators.put(iterator, false);
+        return iterator;
+    }
+
+    /**
+     * return an iterator with additions
+     */
+    public ListIterator<K, V> iteratorWithAdditions() {
+        @SuppressWarnings("unchecked")
+        ListIterator<K, V> iterator = new ListIterator<>(mStart, UNREACHABLE);
+        mIterators.put(iterator, false);
+        return iterator;
+    }
+
+    private static class ListIterator<K, V> implements Iterator<Map.Entry<K, V>> {
+        Entry<K, V> mExpectedEnd;
+        Entry<K, V> mNext;
+
+        ListIterator(Entry<K, V> start, Entry<K, V> expectedEnd) {
+            this.mExpectedEnd = expectedEnd;
+            this.mNext = start;
+        }
+
+        @Override
+        public boolean hasNext() {
+            return mNext != null;
+        }
+
+        void supportRemove(@NonNull Entry<K, V> entry) {
+            if (mExpectedEnd == entry && entry == mNext) {
+                mNext = null;
+                mExpectedEnd = null;
+            }
+
+            if (mExpectedEnd == entry) {
+                mExpectedEnd = mExpectedEnd.mPrevious;
+            }
+
+            if (mNext == entry) {
+                mNext = nextNode();
+            }
+        }
+
+        private Entry<K, V> nextNode() {
+            if (mNext == mExpectedEnd || mExpectedEnd == null) {
+                return null;
+            }
+            return mNext.mNext;
+        }
+
+        @Override
+        public Map.Entry<K, V> next() {
+            Map.Entry<K, V> result = mNext;
+            mNext = nextNode();
+            return result;
+        }
+    }
+
+    @Override
+    public boolean equals(Object obj) {
+        if (obj == this) {
+            return true;
+        }
+        if (!(obj instanceof SafeIterableMap)) {
+            return false;
+        }
+        SafeIterableMap map = (SafeIterableMap) obj;
+        if (this.size() != map.size()) {
+            return false;
+        }
+        ListIterator<K, V> iterator1 = iterator();
+        ListIterator iterator2 = map.iterator();
+        while (iterator1.hasNext() && iterator2.hasNext()) {
+            Map.Entry<K, V> next1 = iterator1.next();
+            Object next2 = iterator2.next();
+            if ((next1 == null && next2 != null)
+                    || (next1 != null && !next1.equals(next2))) {
+                return false;
+            }
+        }
+        return !iterator1.hasNext() && !iterator2.hasNext();
+    }
+
+    @Override
+    public String toString() {
+        StringBuilder builder = new StringBuilder();
+        builder.append("[");
+        ListIterator<K, V> iterator = iterator();
+        while (iterator.hasNext()) {
+            builder.append(iterator.next().toString());
+            if (iterator.hasNext()) {
+                builder.append(", ");
+            }
+        }
+        builder.append("]");
+        return builder.toString();
+    }
+
+    private static class Entry<K, V> implements Map.Entry<K, V> {
+        @NonNull
+        final K mKey;
+        @NonNull
+        final V mValue;
+        Entry<K, V> mNext;
+        Entry<K, V> mPrevious;
+
+        Entry(@NonNull K key, @NonNull V value) {
+            mKey = key;
+            this.mValue = value;
+        }
+
+        @NonNull
+        @Override
+        public K getKey() {
+            return mKey;
+        }
+
+        @NonNull
+        @Override
+        public V getValue() {
+            return mValue;
+        }
+
+        @Override
+        public V setValue(V value) {
+            throw new UnsupportedOperationException("An entry modification is not supported");
+        }
+
+        @Override
+        public String toString() {
+            return mKey + "=" + mValue;
+        }
+
+        @Override
+        public boolean equals(Object obj) {
+            if (obj == this) {
+                return true;
+            }
+            if (!(obj instanceof Entry)) {
+                return false;
+            }
+            Entry entry = (Entry) obj;
+            return mKey.equals(entry.mKey) && mValue.equals(entry.mValue);
+        }
+    }
+}
diff --git a/app-toolkit/common/src/main/java/android/arch/core/util/Function.java b/app-toolkit/common/src/main/java/android/arch/core/util/Function.java
new file mode 100644
index 0000000..25e7a6b
--- /dev/null
+++ b/app-toolkit/common/src/main/java/android/arch/core/util/Function.java
@@ -0,0 +1,33 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.arch.core.util;
+
+/**
+ * Represents a function.
+ *
+ * @param <I> the type of the input to the function
+ * @param <O> the type of the output of the function
+ */
+public interface Function<I, O> {
+    /**
+     * Applies this function to the given input.
+     *
+     * @param input the input
+     * @return the function result.
+     */
+    O apply(I input);
+}
diff --git a/app-toolkit/common/src/test/java/android/arch/core/internal/SafeIterableMapTest.java b/app-toolkit/common/src/test/java/android/arch/core/internal/SafeIterableMapTest.java
new file mode 100644
index 0000000..60a10ab
--- /dev/null
+++ b/app-toolkit/common/src/test/java/android/arch/core/internal/SafeIterableMapTest.java
@@ -0,0 +1,299 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.arch.core.internal;
+
+import static org.hamcrest.CoreMatchers.is;
+import static org.hamcrest.MatcherAssert.assertThat;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+
+import java.util.ArrayList;
+import java.util.Iterator;
+import java.util.Map;
+
+@RunWith(JUnit4.class)
+public class SafeIterableMapTest {
+
+    @Test
+    public void testToString() {
+        SafeIterableMap<Integer, String> map = from(1, 2, 3, 4).to("a", "b", "c", "d");
+        assertThat(map.toString(), is("[1=a, 2=b, 3=c, 4=d]"));
+    }
+
+    @Test
+    public void testEmptyToString() {
+        SafeIterableMap<Integer, Boolean> map = mapOf();
+        assertThat(map.toString(), is("[]"));
+    }
+
+    @Test
+    public void testOneElementToString() {
+        SafeIterableMap<Integer, Boolean> map = mapOf(1);
+        assertThat(map.toString(), is("[1=true]"));
+    }
+
+
+    @Test
+    public void testEquality1() {
+        SafeIterableMap<Integer, Integer> map1 = from(1, 2, 3, 4).to(10, 20, 30, 40);
+        SafeIterableMap<Integer, Integer> map2 = from(1, 2, 3, 4).to(10, 20, 30, 40);
+        assertThat(map1.equals(map2), is(true));
+    }
+
+    @Test
+    public void testEquality2() {
+        SafeIterableMap<Integer, Boolean> map = mapOf(1, 2, 3, 4);
+        //noinspection ObjectEqualsNull
+        assertThat(map.equals(null), is(false));
+    }
+
+    @Test
+    public void testEquality3() {
+        SafeIterableMap<Integer, Boolean> map = mapOf(1, 2, 3, 4);
+        //noinspection EqualsBetweenInconvertibleTypes
+        assertThat(map.equals(new ArrayList<>()), is(false));
+    }
+
+    @Test
+    public void testEquality4() {
+        SafeIterableMap<Integer, Boolean> map = mapOf(1, 2, 3, 4);
+        assertThat(map.equals(new SafeIterableMap<Integer, Boolean>()), is(false));
+    }
+
+    @Test
+    public void testEquality5() {
+        SafeIterableMap<Integer, Boolean> map1 = mapOf(1, 2, 3, 4);
+        SafeIterableMap<Integer, Boolean> map2 = mapOf(1);
+        assertThat(map1.equals(map2), is(false));
+    }
+
+    @Test
+    public void testEquality6() {
+        SafeIterableMap<Integer, Boolean> map1 = mapOf(1, 2, 3, 4);
+        SafeIterableMap<Integer, Boolean> map2 = mapOf(1, 2, 3, 5);
+        assertThat(map1.equals(map2), is(false));
+    }
+
+    @Test
+    public void testEquality7() {
+        SafeIterableMap<Integer, Integer> map1 = from(1, 2, 3, 4).to(1, 2, 3, 4);
+        SafeIterableMap<Integer, Integer> map2 = from(1, 2, 3, 4).to(1, 2, 3, 5);
+        assertThat(map1.equals(map2), is(false));
+    }
+
+
+    @Test
+    public void testEquality8() {
+        SafeIterableMap<Integer, Boolean> map1 = mapOf();
+        SafeIterableMap<Integer, Boolean> map2 = mapOf();
+        assertThat(map1.equals(map2), is(true));
+    }
+
+    @Test
+    public void testEqualityRespectsOrder() {
+        SafeIterableMap<Integer, Boolean> map1 = mapOf(1, 2, 3, 4);
+        SafeIterableMap<Integer, Boolean> map2 = mapOf(1, 3, 2, 4);
+        assertThat(map1.equals(map2), is(false));
+    }
+
+    @Test
+    public void testPut() {
+        SafeIterableMap<Integer, Integer> map = from(1, 2, 3, 4).to(10, 20, 30, 40);
+        assertThat(map.putIfAbsent(5, 10), is((Integer) null));
+        assertThat(map, is(from(1, 2, 3, 4, 5).to(10, 20, 30, 40, 10)));
+    }
+
+    @Test
+    public void testAddExisted() {
+        SafeIterableMap<Integer, Integer> map = from(1, 2, 3, 4).to(10, 20, 261, 40);
+        assertThat(map.putIfAbsent(3, 239), is(261));
+        assertThat(map, is(from(1, 2, 3, 4).to(10, 20, 261, 40)));
+    }
+
+    @Test
+    public void testRemoveLast() {
+        SafeIterableMap<Integer, Integer> map = from(1, 2, 3, 4).to(10, 20, 30, 40);
+        assertThat(map.remove(4), is(40));
+        assertThat(map, is(from(1, 2, 3).to(10, 20, 30)));
+    }
+
+    @Test
+    public void testRemoveFirst() {
+        SafeIterableMap<Integer, Boolean> map = mapOf(1, 2, 3, 4);
+        assertThat(map.remove(1), is(true));
+        assertThat(map, is(mapOf(2, 3, 4)));
+    }
+
+    @Test
+    public void testRemoveMiddle() {
+        SafeIterableMap<Integer, Integer> map = from(1, 2, 3, 4).to(10, 20, 30, 40);
+        assertThat(map.remove(2), is(20));
+        assertThat(map.remove(3), is(30));
+        assertThat(map, is(from(1, 4).to(10, 40)));
+    }
+
+    @Test
+    public void testRemoveNotExisted() {
+        SafeIterableMap<Integer, Boolean> map = mapOf(1, 2, 3, 4);
+        assertThat(map.remove(5), is((Boolean) null));
+        assertThat(map, is(mapOf(1, 2, 3, 4)));
+    }
+
+    @Test
+    public void testRemoveSole() {
+        SafeIterableMap<Integer, Integer> map = from(1).to(261);
+        assertThat(map.remove(1), is(261));
+        assertThat(map, is(new SafeIterableMap<Integer, Integer>()));
+    }
+
+    @Test
+    public void testRemoveDuringIteration1() {
+        SafeIterableMap<Integer, Integer> map = from(1, 2, 3, 4).to(10, 20, 30, 40);
+        int index = 0;
+        int[] expected = new int[]{1, 4};
+        for (Map.Entry<Integer, Integer> i : map) {
+            assertThat(i.getKey(), is(expected[index++]));
+            if (index == 1) {
+                assertThat(map.remove(2), is(20));
+                assertThat(map.remove(3), is(30));
+            }
+        }
+    }
+
+    @Test
+    public void testRemoveDuringIteration2() {
+        SafeIterableMap<Integer, Integer> map = from(1, 2).to(10, 20);
+        Iterator<Map.Entry<Integer, Integer>> iter = map.iterator();
+        assertThat(map.remove(2), is(20));
+        assertThat(map.remove(1), is(10));
+        assertThat(iter.hasNext(), is(false));
+    }
+
+    @Test
+    public void testRemoveDuringIteration3() {
+        SafeIterableMap<Integer, Integer> map = from(1, 2, 3, 4).to(10, 20, 30, 40);
+        int index = 0;
+        Iterator<Map.Entry<Integer, Integer>> iter = map.iterator();
+        assertThat(map.remove(1), is(10));
+        assertThat(map.remove(2), is(20));
+        int[] expected = new int[]{3, 4};
+        while (iter.hasNext()) {
+            assertThat(iter.next().getKey(), is(expected[index++]));
+        }
+    }
+
+    @Test
+    public void testAdditionDuringIteration() {
+        SafeIterableMap<Integer, Boolean> map = mapOf(1, 2, 3, 4);
+        int[] expected = new int[]{1, 2, 3, 4};
+        int index = 0;
+        for (Map.Entry<Integer, Boolean> entry : map) {
+            assertThat(entry.getKey(), is(expected[index++]));
+            if (index == 1) {
+                map.putIfAbsent(5, true);
+            }
+        }
+    }
+
+    @Test
+    public void testReAdditionDuringIteration() {
+        SafeIterableMap<Integer, Boolean> map = mapOf(1, 2, 3, 4);
+        int[] expected = new int[]{1, 2, 4};
+        int index = 0;
+        for (Map.Entry<Integer, Boolean> entry : map) {
+            assertThat(entry.getKey(), is(expected[index++]));
+            if (index == 1) {
+                map.remove(3);
+                map.putIfAbsent(3, true);
+            }
+        }
+    }
+
+    @Test
+    public void testSize() {
+        SafeIterableMap<Integer, Boolean> map = mapOf(1, 2, 3, 4);
+        assertThat(map.size(), is(4));
+        map.putIfAbsent(5, true);
+        map.putIfAbsent(6, true);
+        assertThat(map.size(), is(6));
+        map.remove(5);
+        map.remove(5);
+        assertThat(map.size(), is(5));
+        map.remove(1);
+        map.remove(2);
+        map.remove(4);
+        map.remove(3);
+        map.remove(6);
+        assertThat(map.size(), is(0));
+        map.putIfAbsent(4, true);
+        assertThat(map.size(), is(1));
+        assertThat(mapOf().size(), is(0));
+    }
+
+    @Test
+    public void testIteratorWithAdditions() {
+        SafeIterableMap<Integer, Boolean> map = mapOf(1, 2, 3, 4);
+        int[] expected = new int[]{1, 2, 3, 5};
+        int index = 0;
+        Iterator<Map.Entry<Integer, Boolean>> iterator = map.iteratorWithAdditions();
+        while (iterator.hasNext()) {
+            Map.Entry<Integer, Boolean> entry = iterator.next();
+            assertThat(entry.getKey(), is(expected[index++]));
+            if (index == 3) {
+                map.remove(4);
+                map.putIfAbsent(5, true);
+            }
+        }
+    }
+
+    // for most operations we don't care about values, so we create map from key to true
+    @SafeVarargs
+    private static <K> SafeIterableMap<K, Boolean> mapOf(K... keys) {
+        SafeIterableMap<K, Boolean> map = new SafeIterableMap<>();
+        for (K key : keys) {
+            map.putIfAbsent(key, true);
+        }
+        return map;
+    }
+
+    @SafeVarargs
+    private static <K> MapBuilder<K> from(K... keys) {
+        return new MapBuilder<>(keys);
+    }
+
+    private static class MapBuilder<K> {
+        final K[] mKeys;
+
+        MapBuilder(K[] keys) {
+            this.mKeys = keys;
+        }
+
+        @SafeVarargs
+        public final <V> SafeIterableMap<K, V> to(V... values) {
+            assertThat("Failed to build Map", mKeys.length, is(values.length));
+            SafeIterableMap<K, V> map = new SafeIterableMap<>();
+            for (int i = 0; i < mKeys.length; i++) {
+                map.putIfAbsent(mKeys[i], values[i]);
+            }
+            return map;
+        }
+    }
+}
+
+
diff --git a/app-toolkit/core-testing/build.gradle b/app-toolkit/core-testing/build.gradle
new file mode 100644
index 0000000..c757533
--- /dev/null
+++ b/app-toolkit/core-testing/build.gradle
@@ -0,0 +1,76 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+apply plugin: 'com.android.library'
+apply plugin: 'maven'
+
+android {
+    compileSdkVersion tools.current_sdk
+    buildToolsVersion tools.build_tools_version
+
+    defaultConfig {
+        minSdkVersion flatfoot.min_sdk
+        targetSdkVersion tools.current_sdk
+        versionCode 1
+        versionName "1.0"
+        testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
+    }
+    buildTypes {
+        release {
+            minifyEnabled false
+            proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
+        }
+    }
+
+    compileOptions {
+        sourceCompatibility JavaVersion.VERSION_1_7
+        targetCompatibility JavaVersion.VERSION_1_7
+    }
+}
+
+dependencies {
+    compile project(":arch:runtime")
+    compile libs.support.annotations
+    compile libs.support.core_utils
+    compile(libs.junit) {
+        exclude module: 'hamcrest-core'
+    }
+    compile libs.mockito_core
+
+    testCompile libs.junit
+    testCompile libs.support.annotations
+
+    androidTestCompile libs.junit
+    androidTestCompile(libs.test_runner) {
+        exclude module: 'support-annotations'
+    }
+    androidTestCompile(libs.espresso_core, {
+        exclude group: 'com.android.support', module: 'support-annotations'
+    })
+}
+
+archivesBaseName = "core-testing"
+
+createAndroidCheckstyle(project)
+
+android.libraryVariants.all { variant ->
+    def name = variant.buildType.name
+    def suffix = name.capitalize()
+    project.tasks.create(name: "jar${suffix}", type: Jar) {
+        dependsOn variant.javaCompile
+        from variant.javaCompile.destinationDir
+        destinationDir new File(project.buildDir, "libJar")
+    }
+}
diff --git a/app-toolkit/core-testing/src/androidTest/java/android/arch/core/executor/testing/CountingTaskExecutorRuleTest.java b/app-toolkit/core-testing/src/androidTest/java/android/arch/core/executor/testing/CountingTaskExecutorRuleTest.java
new file mode 100644
index 0000000..ad36b9b
--- /dev/null
+++ b/app-toolkit/core-testing/src/androidTest/java/android/arch/core/executor/testing/CountingTaskExecutorRuleTest.java
@@ -0,0 +1,181 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.arch.core.executor.testing;
+
+import static org.hamcrest.CoreMatchers.is;
+import static org.hamcrest.MatcherAssert.assertThat;
+
+import android.arch.core.executor.AppToolkitTaskExecutor;
+import android.support.test.filters.MediumTest;
+import android.support.test.runner.AndroidJUnit4;
+
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.Semaphore;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.TimeoutException;
+
+@RunWith(AndroidJUnit4.class)
+@MediumTest
+public class CountingTaskExecutorRuleTest {
+    private final Semaphore mOnIdleCount = new Semaphore(0);
+
+    @Rule
+    public CountingTaskExecutorRule mRule = new CountingTaskExecutorRule() {
+        @Override
+        protected void onIdle() {
+            super.onIdle();
+            mOnIdleCount.release(1);
+        }
+    };
+
+    @Test
+    public void initialIdle() {
+        assertThat(mRule.isIdle(), is(true));
+    }
+
+    @Test
+    public void busyIO() throws InterruptedException {
+        LatchRunnable task = runOnIO();
+        singleTaskTest(task);
+    }
+
+    @Test
+    public void busyMain() throws InterruptedException {
+        LatchRunnable task = runOnMain();
+        singleTaskTest(task);
+    }
+
+    @Test
+    public void multipleTasks() throws InterruptedException {
+        List<LatchRunnable> latches = new ArrayList<>(10);
+        for (int i = 0; i < 5; i++) {
+            latches.add(runOnIO());
+            latches.add(runOnMain());
+        }
+        assertNotIdle();
+        for (int i = 0; i < 9; i++) {
+            latches.get(i).start();
+        }
+        for (int i = 0; i < 9; i++) {
+            latches.get(i).await();
+        }
+        assertNotIdle();
+
+        LatchRunnable another = runOnIO();
+        latches.get(9).startAndFinish();
+        assertNotIdle();
+
+        another.startAndFinish();
+        assertBecomeIdle();
+
+        LatchRunnable oneMore = runOnMain();
+
+        assertNotIdle();
+
+        oneMore.startAndFinish();
+        assertBecomeIdle();
+    }
+
+    private void assertNotIdle() throws InterruptedException {
+        assertThat(mOnIdleCount.tryAcquire(300, TimeUnit.MILLISECONDS), is(false));
+        assertThat(mRule.isIdle(), is(false));
+    }
+
+    private void assertBecomeIdle() throws InterruptedException {
+        assertThat(mOnIdleCount.tryAcquire(1, TimeUnit.SECONDS), is(true));
+        assertThat(mRule.isIdle(), is(true));
+    }
+
+    private void singleTaskTest(LatchRunnable task)
+            throws InterruptedException {
+        assertNotIdle();
+        task.startAndFinish();
+        assertBecomeIdle();
+    }
+
+    private LatchRunnable runOnIO() {
+        LatchRunnable latchRunnable = new LatchRunnable();
+        AppToolkitTaskExecutor.getInstance().executeOnDiskIO(latchRunnable);
+        return latchRunnable;
+    }
+
+    private LatchRunnable runOnMain() {
+        LatchRunnable latchRunnable = new LatchRunnable();
+        AppToolkitTaskExecutor.getInstance().executeOnMainThread(latchRunnable);
+        return latchRunnable;
+    }
+
+    @Test
+    public void drainFailure() throws InterruptedException {
+        runOnIO();
+        try {
+            mRule.drainTasks(300, TimeUnit.MILLISECONDS);
+            throw new AssertionError("drain should fail");
+        } catch (TimeoutException ignored) {
+        }
+    }
+
+    @Test
+    public void drainSuccess() throws TimeoutException, InterruptedException {
+        final LatchRunnable task = runOnIO();
+        new Thread(new Runnable() {
+            @Override
+            public void run() {
+                try {
+                    Thread.sleep(300);
+                } catch (InterruptedException ignored) {
+                }
+                task.start();
+            }
+        }).start();
+        mRule.drainTasks(1, TimeUnit.SECONDS);
+    }
+
+    private static class LatchRunnable implements Runnable {
+        private final CountDownLatch mStart = new CountDownLatch(1);
+        private final CountDownLatch mEnd = new CountDownLatch(1);
+
+        @Override
+        public void run() {
+            try {
+                mStart.await(10, TimeUnit.SECONDS);
+                mEnd.countDown();
+            } catch (InterruptedException e) {
+                throw new AssertionError(e);
+            }
+        }
+
+        void await() throws InterruptedException {
+            mEnd.await(10, TimeUnit.SECONDS);
+        }
+
+        void start() {
+            mStart.countDown();
+        }
+
+        private void startAndFinish() throws InterruptedException {
+            start();
+            await();
+        }
+    }
+}
diff --git a/app-toolkit/core-testing/src/main/AndroidManifest.xml b/app-toolkit/core-testing/src/main/AndroidManifest.xml
new file mode 100644
index 0000000..83e0d83
--- /dev/null
+++ b/app-toolkit/core-testing/src/main/AndroidManifest.xml
@@ -0,0 +1,18 @@
+<!--
+  ~ Copyright (C) 2017 The Android Open Source Project
+  ~
+  ~ Licensed under the Apache License, Version 2.0 (the "License");
+  ~ you may not use this file except in compliance with the License.
+  ~ You may obtain a copy of the License at
+  ~
+  ~      http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing, software
+  ~ distributed under the License is distributed on an "AS IS" BASIS,
+  ~ WITHOUT 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 package="android.arch.core.testing">
+</manifest>
diff --git a/app-toolkit/core-testing/src/main/java/android/arch/core/executor/JunitTaskExecutorRule.java b/app-toolkit/core-testing/src/main/java/android/arch/core/executor/JunitTaskExecutorRule.java
new file mode 100644
index 0000000..cd4f8f5
--- /dev/null
+++ b/app-toolkit/core-testing/src/main/java/android/arch/core/executor/JunitTaskExecutorRule.java
@@ -0,0 +1,94 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.arch.core.executor;
+
+import android.support.annotation.RestrictTo;
+
+import org.junit.rules.TestRule;
+import org.junit.runner.Description;
+import org.junit.runners.model.MultipleFailureException;
+import org.junit.runners.model.Statement;
+import org.mockito.Mockito;
+
+import java.util.List;
+
+/**
+ * A JUnit rule that swaps the task executor with a more controllable one.
+ * Once we have the TaskExecutor API, we should consider making this public (via some test package).
+ *
+ * @hide
+ */
+@RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
+public class JunitTaskExecutorRule implements TestRule {
+    private final TaskExecutorWithFakeMainThread mTaskExecutor;
+
+    public JunitTaskExecutorRule(int ioThreadCount, boolean spyOnExecutor) {
+        if (spyOnExecutor) {
+            mTaskExecutor = Mockito.spy(new TaskExecutorWithFakeMainThread(ioThreadCount));
+        } else {
+            mTaskExecutor = new TaskExecutorWithFakeMainThread(ioThreadCount);
+        }
+
+    }
+
+    private void beforeStart() {
+        AppToolkitTaskExecutor.getInstance().setDelegate(mTaskExecutor);
+    }
+
+    private void afterFinished() {
+        AppToolkitTaskExecutor.getInstance().setDelegate(null);
+    }
+
+    public TaskExecutor getTaskExecutor() {
+        return mTaskExecutor;
+    }
+
+    /**
+     * Awaits while all currently posted tasks will be finished
+     *
+     * @param seconds timeout in seconds
+     */
+    public void drainTasks(int seconds) throws InterruptedException {
+        mTaskExecutor.drainTasks(seconds);
+    }
+
+    @Override
+    public Statement apply(final Statement base, Description description) {
+        return new Statement() {
+            @Override
+            public void evaluate() throws Throwable {
+                beforeStart();
+                try {
+                    base.evaluate();
+                    finishExecutors();
+                } catch (Throwable t) {
+                    throw new RuntimeException(t);
+                } finally {
+                    afterFinished();
+                }
+            }
+        };
+    }
+
+    private void finishExecutors() throws InterruptedException, MultipleFailureException {
+        mTaskExecutor.shutdown(10);
+        final List<Throwable> errors = mTaskExecutor.getErrors();
+        if (!errors.isEmpty()) {
+            throw new MultipleFailureException(errors);
+        }
+    }
+}
diff --git a/app-toolkit/core-testing/src/main/java/android/arch/core/executor/TaskExecutorWithFakeMainThread.java b/app-toolkit/core-testing/src/main/java/android/arch/core/executor/TaskExecutorWithFakeMainThread.java
new file mode 100644
index 0000000..af0aca4
--- /dev/null
+++ b/app-toolkit/core-testing/src/main/java/android/arch/core/executor/TaskExecutorWithFakeMainThread.java
@@ -0,0 +1,154 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.arch.core.executor;
+
+import android.support.annotation.NonNull;
+import android.support.annotation.RestrictTo;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.Executors;
+import java.util.concurrent.ThreadFactory;
+import java.util.concurrent.TimeUnit;
+
+/**
+ * A TaskExecutor that has a real thread for main thread operations and can wait for execution etc.
+ *
+ * @hide
+ */
+@RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
+public class TaskExecutorWithFakeMainThread extends TaskExecutor {
+    private List<Throwable> mCaughtExceptions = Collections.synchronizedList(new ArrayList
+            <Throwable>());
+
+    private ExecutorService mIOService;
+
+    private Thread mMainThread;
+    private final int mIOThreadCount;
+
+    private ExecutorService mMainThreadService =
+            Executors.newSingleThreadExecutor(new ThreadFactory() {
+                @Override
+                public Thread newThread(@NonNull final Runnable r) {
+                    mMainThread = new LoggingThread(r);
+                    return mMainThread;
+                }
+            });
+
+    public TaskExecutorWithFakeMainThread(int ioThreadCount) {
+        mIOThreadCount = ioThreadCount;
+        mIOService = Executors.newFixedThreadPool(ioThreadCount, new ThreadFactory() {
+            @Override
+            public Thread newThread(@NonNull Runnable r) {
+                return new LoggingThread(r);
+            }
+        });
+    }
+
+    @Override
+    public void executeOnDiskIO(Runnable runnable) {
+        mIOService.execute(runnable);
+    }
+
+    @Override
+    public void postToMainThread(Runnable runnable) {
+        // Tasks in SingleThreadExecutor are guaranteed to execute sequentially,
+        // and no more than one task will be active at any given time.
+        // So if we call this method from the main thread, new task will be scheduled,
+        // which is equivalent to post.
+        mMainThreadService.execute(runnable);
+    }
+
+    @Override
+    public boolean isMainThread() {
+        return Thread.currentThread() == mMainThread;
+    }
+
+    List<Throwable> getErrors() {
+        return mCaughtExceptions;
+    }
+
+    @SuppressWarnings("SameParameterValue")
+    void shutdown(int timeoutInSeconds) throws InterruptedException {
+        mMainThreadService.shutdown();
+        mIOService.shutdown();
+        mMainThreadService.awaitTermination(timeoutInSeconds, TimeUnit.SECONDS);
+        mIOService.awaitTermination(timeoutInSeconds, TimeUnit.SECONDS);
+    }
+
+    /**
+     * Drains tasks at the given time limit
+     * @param seconds Number of seconds to wait
+     * @throws InterruptedException
+     */
+    public void drainTasks(int seconds) throws InterruptedException {
+        if (isMainThread()) {
+            throw new IllegalStateException();
+        }
+        final CountDownLatch enterLatch = new CountDownLatch(mIOThreadCount);
+        final CountDownLatch exitLatch = new CountDownLatch(1);
+        for (int i = 0; i < mIOThreadCount; i++) {
+            executeOnDiskIO(new Runnable() {
+                @Override
+                public void run() {
+                    enterLatch.countDown();
+                    try {
+                        exitLatch.await();
+                    } catch (InterruptedException e) {
+                        throw new RuntimeException(e);
+                    }
+                }
+            });
+        }
+
+        final CountDownLatch mainLatch = new CountDownLatch(1);
+        postToMainThread(new Runnable() {
+            @Override
+            public void run() {
+                mainLatch.countDown();
+            }
+        });
+        if (!enterLatch.await(seconds, TimeUnit.SECONDS)) {
+            throw new AssertionError("Could not drain IO tasks in " + seconds
+                    + " seconds");
+        }
+        exitLatch.countDown();
+        if (!mainLatch.await(seconds, TimeUnit.SECONDS)) {
+            throw new AssertionError("Could not drain UI tasks in " + seconds
+                    + " seconds");
+        }
+    }
+
+    @SuppressWarnings("WeakerAccess")
+    class LoggingThread extends Thread {
+        LoggingThread(final Runnable target) {
+            super(new Runnable() {
+                @Override
+                public void run() {
+                    try {
+                        target.run();
+                    } catch (Throwable t) {
+                        mCaughtExceptions.add(t);
+                    }
+                }
+            });
+        }
+    }
+}
diff --git a/app-toolkit/core-testing/src/main/java/android/arch/core/executor/testing/CountingTaskExecutorRule.java b/app-toolkit/core-testing/src/main/java/android/arch/core/executor/testing/CountingTaskExecutorRule.java
new file mode 100644
index 0000000..ad930aa
--- /dev/null
+++ b/app-toolkit/core-testing/src/main/java/android/arch/core/executor/testing/CountingTaskExecutorRule.java
@@ -0,0 +1,141 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.arch.core.executor.testing;
+
+import android.arch.core.executor.AppToolkitTaskExecutor;
+import android.arch.core.executor.DefaultTaskExecutor;
+import android.os.SystemClock;
+
+import org.junit.rules.TestWatcher;
+import org.junit.runner.Description;
+
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.TimeoutException;
+
+/**
+ * A JUnit Test Rule that swaps the background executor used by the Architecture Components with a
+ * different one which counts the tasks as they are start and finish.
+ * <p>
+ * You can use this rule for your host side tests that use Architecture Components.
+ */
+public class CountingTaskExecutorRule extends TestWatcher {
+    private final Object mCountLock = new Object();
+    private int mTaskCount = 0;
+
+    @Override
+    protected void starting(Description description) {
+        super.starting(description);
+        AppToolkitTaskExecutor.getInstance().setDelegate(new DefaultTaskExecutor() {
+            @Override
+            public void executeOnDiskIO(Runnable runnable) {
+                super.executeOnDiskIO(new CountingRunnable(runnable));
+            }
+
+            @Override
+            public void postToMainThread(Runnable runnable) {
+                super.postToMainThread(new CountingRunnable(runnable));
+            }
+        });
+    }
+
+    @Override
+    protected void finished(Description description) {
+        super.finished(description);
+        AppToolkitTaskExecutor.getInstance().setDelegate(null);
+    }
+
+    private void increment() {
+        synchronized (mCountLock) {
+            mTaskCount++;
+        }
+    }
+
+    private void decrement() {
+        synchronized (mCountLock) {
+            mTaskCount--;
+            if (mTaskCount == 0) {
+                onIdle();
+                mCountLock.notifyAll();
+            }
+        }
+    }
+
+    /**
+     * Called when the number of awaiting tasks reaches to 0.
+     *
+     * @see #isIdle()
+     */
+    protected void onIdle() {
+
+    }
+
+    /**
+     * Returns false if there are tasks waiting to be executed, true otherwise.
+     *
+     * @return False if there are tasks waiting to be executed, true otherwise.
+     *
+     * @see #onIdle()
+     */
+    public boolean isIdle() {
+        synchronized (mCountLock) {
+            return mTaskCount == 0;
+        }
+    }
+
+    /**
+     * Waits until all active tasks are finished.
+     *
+     * @param time The duration to wait
+     * @param timeUnit The time unit for the {@code time} parameter
+     *
+     * @throws InterruptedException If thread is interrupted while waiting
+     * @throws TimeoutException If tasks cannot be drained at the given time
+     */
+    public void drainTasks(int time, TimeUnit timeUnit)
+            throws InterruptedException, TimeoutException {
+        long end = SystemClock.uptimeMillis() + timeUnit.toMillis(time);
+        synchronized (mCountLock) {
+            while (mTaskCount != 0) {
+                long now = SystemClock.uptimeMillis();
+                long remaining = end - now;
+                if (remaining > 0) {
+                    mCountLock.wait(remaining);
+                } else {
+                    throw new TimeoutException("could not drain tasks");
+                }
+            }
+        }
+    }
+
+    class CountingRunnable implements Runnable {
+        final Runnable mWrapped;
+
+        CountingRunnable(Runnable wrapped) {
+            mWrapped = wrapped;
+            increment();
+        }
+
+        @Override
+        public void run() {
+            try {
+                mWrapped.run();
+            } finally {
+                decrement();
+            }
+        }
+    }
+}
diff --git a/app-toolkit/core-testing/src/main/java/android/arch/core/executor/testing/InstantTaskExecutorRule.java b/app-toolkit/core-testing/src/main/java/android/arch/core/executor/testing/InstantTaskExecutorRule.java
new file mode 100644
index 0000000..07dcf1f
--- /dev/null
+++ b/app-toolkit/core-testing/src/main/java/android/arch/core/executor/testing/InstantTaskExecutorRule.java
@@ -0,0 +1,58 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.arch.core.executor.testing;
+
+import android.arch.core.executor.AppToolkitTaskExecutor;
+import android.arch.core.executor.TaskExecutor;
+
+import org.junit.rules.TestWatcher;
+import org.junit.runner.Description;
+
+/**
+ * A JUnit Test Rule that swaps the background executor used by the Architecture Components with a
+ * different one which executes each task synchronously.
+ * <p>
+ * You can use this rule for your host side tests that use Architecture Components.
+ */
+public class InstantTaskExecutorRule extends TestWatcher {
+    @Override
+    protected void starting(Description description) {
+        super.starting(description);
+        AppToolkitTaskExecutor.getInstance().setDelegate(new TaskExecutor() {
+            @Override
+            public void executeOnDiskIO(Runnable runnable) {
+                runnable.run();
+            }
+
+            @Override
+            public void postToMainThread(Runnable runnable) {
+                runnable.run();
+            }
+
+            @Override
+            public boolean isMainThread() {
+                return true;
+            }
+        });
+    }
+
+    @Override
+    protected void finished(Description description) {
+        super.finished(description);
+        AppToolkitTaskExecutor.getInstance().setDelegate(null);
+    }
+}
diff --git a/app-toolkit/core-testing/src/test/java/android/arch/core/executor/testing/InstantTaskExecutorRuleTest.java b/app-toolkit/core-testing/src/test/java/android/arch/core/executor/testing/InstantTaskExecutorRuleTest.java
new file mode 100644
index 0000000..4345fd1
--- /dev/null
+++ b/app-toolkit/core-testing/src/test/java/android/arch/core/executor/testing/InstantTaskExecutorRuleTest.java
@@ -0,0 +1,66 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.arch.core.executor.testing;
+
+import static org.junit.Assert.assertTrue;
+
+import android.arch.core.executor.AppToolkitTaskExecutor;
+
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+
+import java.util.concurrent.Callable;
+import java.util.concurrent.ExecutionException;
+import java.util.concurrent.FutureTask;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.TimeoutException;
+
+@RunWith(JUnit4.class)
+public class InstantTaskExecutorRuleTest {
+    @Rule
+    public InstantTaskExecutorRule mInstantTaskExecutorRule = new InstantTaskExecutorRule();
+
+    @Test
+    public void executeOnMain() throws ExecutionException, InterruptedException, TimeoutException {
+        final Thread current = Thread.currentThread();
+        FutureTask<Void> check = new FutureTask<>(new Callable<Void>() {
+            @Override
+            public Void call() throws Exception {
+                assertTrue(Thread.currentThread() == current);
+                return null;
+            }
+        });
+        AppToolkitTaskExecutor.getInstance().executeOnMainThread(check);
+        check.get(1, TimeUnit.SECONDS);
+    }
+
+    @Test
+    public void executeOnIO() throws ExecutionException, InterruptedException, TimeoutException {
+        final Thread current = Thread.currentThread();
+        FutureTask<Void> check = new FutureTask<>(new Callable<Void>() {
+            @Override
+            public Void call() throws Exception {
+                assertTrue(Thread.currentThread() == current);
+                return null;
+            }
+        });
+        AppToolkitTaskExecutor.getInstance().executeOnDiskIO(check);
+        check.get(1, TimeUnit.SECONDS);
+    }
+}
diff --git a/app-toolkit/dependencies.gradle b/app-toolkit/dependencies.gradle
new file mode 100644
index 0000000..b09adfe
--- /dev/null
+++ b/app-toolkit/dependencies.gradle
@@ -0,0 +1,84 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+def ffLibs
+if (hasProperty("libs")) {
+    ffLibs = libs
+} else {
+    apply from: "${ext.supportRootFolder}/buildSrc/dependencies.gradle"
+    ffLibs = libs
+}
+def ffVersions = [:]
+ffVersions.kotlin = "1.1.1"
+ffVersions.auto_common = "0.6"
+ffVersions.javapoet = "1.8.0"
+ffVersions.compile_testing = "0.9"
+ffVersions.localize_maven = "1.2"
+ffVersions.support_lib = "25.3.1"
+ffVersions.intellij_annotations = "12.0"
+ffVersions.rxjava2 = "2.0.6"
+ffVersions.reactivestreams = "1.0.0"
+// this Xerial version is newer than we want but we need it to fix
+// https://github.com/xerial/sqlite-jdbc/issues/97
+ffVersions.xerial = "3.16.1"
+ffVersions.antlr = "4.5.3"
+ffVersions.commons_codec = "1.10"
+ffVersions.gson = "2.8.0"
+
+ffLibs.kotlin = [
+        stdlib : "org.jetbrains.kotlin:kotlin-stdlib:$ffVersions.kotlin",
+        gradle_plugin : "org.jetbrains.kotlin:kotlin-gradle-plugin:$ffVersions.kotlin"
+]
+ffLibs.auto_common = "com.google.auto:auto-common:$ffVersions.auto_common"
+ffLibs.apache = [
+    commons : [
+            codec : "commons-codec:commons-codec:$ffVersions.commons_codec"
+    ]
+]
+
+def getSupportLib(String name, String version, String artifactName = null) {
+    def sourceProject = findProject(name)
+    if (sourceProject != null) {
+        return sourceProject
+    }
+    if (artifactName == null) {
+        artifactName = name
+    }
+    return "com.android.support$artifactName:$version"
+}
+ffLibs.support = [
+        annotations : getSupportLib(":support-annotations", ffVersions.support_lib),
+        core_utils : getSupportLib(':support-core-utils', ffVersions.support_lib),
+        fragments : getSupportLib(':support-fragment', ffVersions.support_lib),
+        app_compat : getSupportLib(':support-appcompat-v7', ffVersions.support_lib, ":appcompat-v7")
+]
+
+ffLibs.localize_maven = "com.android.databinding:localizemaven:$ffVersions.localize_maven"
+ffLibs.javapoet = "com.squareup:javapoet:$ffVersions.javapoet"
+ffLibs.antlr = "org.antlr:antlr4:$ffVersions.antlr"
+ffLibs.xerial = "org.xerial:sqlite-jdbc:$ffVersions.xerial"
+ffLibs.google_compile_testing = "com.google.testing.compile:compile-testing:$ffVersions.compile_testing"
+ffLibs.ij_annotations = "com.intellij:annotations:$ffVersions.intellij_annotations"
+ffLibs.reactive_streams = "org.reactivestreams:reactive-streams:$ffVersions.reactivestreams"
+ffLibs.rx_java = "io.reactivex.rxjava2:rxjava:$ffVersions.rxjava2"
+ffLibs.gson = "com.google.code.gson:gson:$ffVersions.gson"
+
+ext.tools = [:]
+ext.tools.current_sdk = gradle.ext.currentSdk
+ext.tools.build_tools_version = rootProject.ext.buildToolsVersion
+ext.flatfoot = [:]
+ext.flatfoot.release_version = "1.0.0-alpha4"
+ext.flatfoot.min_sdk = 14
diff --git a/app-toolkit/gradle.properties b/app-toolkit/gradle.properties
new file mode 100644
index 0000000..3e1a1a6
--- /dev/null
+++ b/app-toolkit/gradle.properties
@@ -0,0 +1,4 @@
+org.gradle.jvmargs=-Xmx3000M
+org.gradle.daemon=true
+org.gradle.configureondemand=false
+org.gradle.parallel=false
\ No newline at end of file
diff --git a/app-toolkit/gradle/wrapper/gradle-wrapper.jar b/app-toolkit/gradle/wrapper/gradle-wrapper.jar
new file mode 100644
index 0000000..d6e2637
--- /dev/null
+++ b/app-toolkit/gradle/wrapper/gradle-wrapper.jar
Binary files differ
diff --git a/app-toolkit/gradle/wrapper/gradle-wrapper.properties b/app-toolkit/gradle/wrapper/gradle-wrapper.properties
new file mode 100644
index 0000000..1b987ef
--- /dev/null
+++ b/app-toolkit/gradle/wrapper/gradle-wrapper.properties
@@ -0,0 +1,6 @@
+#Tue Aug 16 10:43:36 PDT 2016
+distributionBase=GRADLE_USER_HOME
+distributionPath=wrapper/dists
+zipStoreBase=GRADLE_USER_HOME
+zipStorePath=wrapper/dists
+distributionUrl=../../../../../tools/external/gradle/gradle-4.1-milestone-1-bin.zip
diff --git a/app-toolkit/gradlew b/app-toolkit/gradlew
new file mode 100755
index 0000000..4ef3a87
--- /dev/null
+++ b/app-toolkit/gradlew
@@ -0,0 +1,171 @@
+#!/usr/bin/env sh
+
+##############################################################################
+##
+##  Gradle start up script for UN*X
+##
+##############################################################################
+
+# Attempt to set APP_HOME
+# Resolve links: $0 may be a link
+PRG="$0"
+# Need this for relative symlinks.
+while [ -h "$PRG" ] ; do
+    ls=`ls -ld "$PRG"`
+    link=`expr "$ls" : '.*-> \(.*\)$'`
+    if expr "$link" : '/.*' > /dev/null; then
+        PRG="$link"
+    else
+        PRG=`dirname "$PRG"`"/$link"
+    fi
+done
+SAVED="`pwd`"
+cd "`dirname \"$PRG\"`/" >/dev/null
+APP_HOME="`pwd -P`"
+cd "$SAVED" >/dev/null
+
+APP_NAME="Gradle"
+APP_BASE_NAME=`basename "$0"`
+
+# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
+DEFAULT_JVM_OPTS=""
+
+# Use the maximum available, or set MAX_FD != -1 to use that value.
+MAX_FD="maximum"
+
+warn ( ) {
+    echo "$*"
+}
+
+die ( ) {
+    echo
+    echo "$*"
+    echo
+    exit 1
+}
+
+# OS specific support (must be 'true' or 'false').
+cygwin=false
+msys=false
+darwin=false
+nonstop=false
+case "`uname`" in
+  CYGWIN* )
+    cygwin=true
+    ;;
+  Darwin* )
+    darwin=true
+    ;;
+  MINGW* )
+    msys=true
+    ;;
+  NONSTOP* )
+    nonstop=true
+    ;;
+esac
+
+CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
+
+# Determine the Java command to use to start the JVM.
+if [ -n "$JAVA_HOME" ] ; then
+    if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
+        # IBM's JDK on AIX uses strange locations for the executables
+        JAVACMD="$JAVA_HOME/jre/sh/java"
+    else
+        JAVACMD="$JAVA_HOME/bin/java"
+    fi
+    if [ ! -x "$JAVACMD" ] ; then
+        die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
+
+Please set the JAVA_HOME variable in your environment to match the
+location of your Java installation."
+    fi
+else
+    JAVACMD="java"
+    which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
+
+Please set the JAVA_HOME variable in your environment to match the
+location of your Java installation."
+fi
+
+# Increase the maximum file descriptors if we can.
+if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then
+    MAX_FD_LIMIT=`ulimit -H -n`
+    if [ $? -eq 0 ] ; then
+        if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then
+            MAX_FD="$MAX_FD_LIMIT"
+        fi
+        ulimit -n $MAX_FD
+        if [ $? -ne 0 ] ; then
+            warn "Could not set maximum file descriptor limit: $MAX_FD"
+        fi
+    else
+        warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT"
+    fi
+fi
+
+# For Darwin, add options to specify how the application appears in the dock
+if $darwin; then
+    GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\""
+fi
+
+# For Cygwin, switch paths to Windows format before running java
+if $cygwin ; then
+    APP_HOME=`cygpath --path --mixed "$APP_HOME"`
+    CLASSPATH=`cygpath --path --mixed "$CLASSPATH"`
+    JAVACMD=`cygpath --unix "$JAVACMD"`
+
+    # We build the pattern for arguments to be converted via cygpath
+    ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null`
+    SEP=""
+    for dir in $ROOTDIRSRAW ; do
+        ROOTDIRS="$ROOTDIRS$SEP$dir"
+        SEP="|"
+    done
+    OURCYGPATTERN="(^($ROOTDIRS))"
+    # Add a user-defined pattern to the cygpath arguments
+    if [ "$GRADLE_CYGPATTERN" != "" ] ; then
+        OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)"
+    fi
+    # Now convert the arguments - kludge to limit ourselves to /bin/sh
+    i=0
+    for arg in "$@" ; do
+        CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -`
+        CHECK2=`echo "$arg"|egrep -c "^-"`                                 ### Determine if an option
+
+        if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then                    ### Added a condition
+            eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"`
+        else
+            eval `echo args$i`="\"$arg\""
+        fi
+        i=$((i+1))
+    done
+    case $i in
+        (0) set -- ;;
+        (1) set -- "$args0" ;;
+        (2) set -- "$args0" "$args1" ;;
+        (3) set -- "$args0" "$args1" "$args2" ;;
+        (4) set -- "$args0" "$args1" "$args2" "$args3" ;;
+        (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;;
+        (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;;
+        (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;;
+        (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;;
+        (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;;
+    esac
+fi
+
+# Escape application args
+for s in "${@}" ; do
+    s=\"$s\"
+    APP_ARGS=$APP_ARGS" "$s
+done
+
+# Collect all arguments for the java command, following the shell quoting and substitution rules
+eval set -- "$DEFAULT_JVM_OPTS" "$JAVA_OPTS" "$GRADLE_OPTS" "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS"
+
+# by default we should be in the correct project dir, but when run from Finder on Mac, the cwd is wrong
+if [ "$(uname)" = "Darwin" ] && [ "$HOME" = "$PWD" ]; then
+  cd "$(dirname "$0")"
+fi
+
+exec "$JAVACMD" "$@"
diff --git a/app-toolkit/gradlew.bat b/app-toolkit/gradlew.bat
new file mode 100644
index 0000000..e95643d
--- /dev/null
+++ b/app-toolkit/gradlew.bat
@@ -0,0 +1,84 @@
+@if "%DEBUG%" == "" @echo off

+@rem ##########################################################################

+@rem

+@rem  Gradle startup script for Windows

+@rem

+@rem ##########################################################################

+

+@rem Set local scope for the variables with windows NT shell

+if "%OS%"=="Windows_NT" setlocal

+

+set DIRNAME=%~dp0

+if "%DIRNAME%" == "" set DIRNAME=.

+set APP_BASE_NAME=%~n0

+set APP_HOME=%DIRNAME%

+

+@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.

+set DEFAULT_JVM_OPTS=

+

+@rem Find java.exe

+if defined JAVA_HOME goto findJavaFromJavaHome

+

+set JAVA_EXE=java.exe

+%JAVA_EXE% -version >NUL 2>&1

+if "%ERRORLEVEL%" == "0" goto init

+

+echo.

+echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.

+echo.

+echo Please set the JAVA_HOME variable in your environment to match the

+echo location of your Java installation.

+

+goto fail

+

+:findJavaFromJavaHome

+set JAVA_HOME=%JAVA_HOME:"=%

+set JAVA_EXE=%JAVA_HOME%/bin/java.exe

+

+if exist "%JAVA_EXE%" goto init

+

+echo.

+echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%

+echo.

+echo Please set the JAVA_HOME variable in your environment to match the

+echo location of your Java installation.

+

+goto fail

+

+:init

+@rem Get command-line arguments, handling Windows variants

+

+if not "%OS%" == "Windows_NT" goto win9xME_args

+

+:win9xME_args

+@rem Slurp the command line arguments.

+set CMD_LINE_ARGS=

+set _SKIP=2

+

+:win9xME_args_slurp

+if "x%~1" == "x" goto execute

+

+set CMD_LINE_ARGS=%*

+

+:execute

+@rem Setup the command line

+

+set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar

+

+@rem Execute Gradle

+"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS%

+

+:end

+@rem End local scope for the variables with windows NT shell

+if "%ERRORLEVEL%"=="0" goto mainEnd

+

+:fail

+rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of

+rem the _cmd.exe /c_ return code!

+if  not "" == "%GRADLE_EXIT_CONSOLE%" exit 1

+exit /b 1

+

+:mainEnd

+if "%OS%"=="Windows_NT" endlocal

+

+:omega

diff --git a/app-toolkit/init.gradle b/app-toolkit/init.gradle
new file mode 100644
index 0000000..13222bf
--- /dev/null
+++ b/app-toolkit/init.gradle
@@ -0,0 +1,292 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+import org.gradle.internal.os.OperatingSystem
+
+def root = ext.supportRootFolder
+ext.usePrebuilts = "true" // for doclava
+ext.inAppToolkitProject = rootProject.name == "app-toolkit"
+
+if (ext.inAppToolkitProject) {
+    apply from: "${ext.supportRootFolder}/buildSrc/init.gradle"
+    init.loadDefaultVersions()
+    init.setSdkInLocalPropertiesFile()
+}
+
+def checkoutRoot = "${root}/../.."
+ext.checkoutRoot = checkoutRoot
+ext.prebuiltsRoot = "$checkoutRoot/prebuilts"
+ext.prebuiltsRootUri = "file://${prebuiltsRoot}"
+
+final String platform = OperatingSystem.current().isMacOsX() ? 'darwin' : 'linux'
+final String fullSdkPath = new File("${checkoutRoot}/prebuilts/fullsdk-${platform}").getCanonicalPath()
+System.setProperty('android.home', fullSdkPath)
+File props = file("local.properties")
+props.write "sdk.dir=${fullSdkPath}"
+
+def buildDir
+def distDir
+def supportLibBuildDir
+
+if (ext.runningInBuildServer) {
+    supportLibBuildDir = new File(System.env.OUT_DIR + '/gradle/frameworks/support/build').getCanonicalFile();
+    buildDir = new File(System.env.OUT_DIR + '/gradle/frameworks/app-toolkit/build').getCanonicalFile()
+    distDir = new File(System.env.DIST_DIR).getCanonicalFile()
+} else {
+    supportLibBuildDir = file("${checkoutRoot}/out/host/gradle/frameworks/support/build")
+    buildDir = file("${checkoutRoot}/out/host/gradle/frameworks/app-toolkit/build")
+    distDir = file("${checkoutRoot}/out/dist")
+}
+
+def localMavenRepo = "file://${new File(buildDir, "flatfoot_repo").absolutePath}"
+ext.testApkDistOut = distDir
+ext.testResultsDistDir = new File(distDir, "host-test-reports")
+ext.localMavenRepo = localMavenRepo
+file(localMavenRepo).delete()
+file(localMavenRepo).mkdirs()
+
+ext.repoNames = ["$prebuiltsRootUri/maven_repo",
+                 "$prebuiltsRootUri/gradle-plugin",
+                 "$prebuiltsRootUri/tools/common/m2/repository",
+                 "$prebuiltsRootUri/tools/common/m2/internal",
+                 "$prebuiltsRootUri/tools/common/offline-m2",
+                 "$prebuiltsRootUri/maven_repo/android",
+                 "file://$fullSdkPath/extras/android/m2repository",
+                 "file://${new File(supportLibBuildDir, "support_repo").absolutePath}"]
+
+apply from: "${ext.supportRootFolder}/app-toolkit/dependencies.gradle"
+ext.enablePublicRepos = System.getenv("ALLOW_PUBLIC_REPOS")
+
+// repository creation task
+def buildServerAnchorTask = rootProject.tasks.create(name : "runBuildServerCompilationTasks",
+    description: "Anchor task for everything we want to run in build server.")
+
+if (ext.inAppToolkitProject) {
+    // always build offline docs for flatfoot specific builds.
+    ext.docs.dac = [
+            libraryroot: "android/arch",
+            dataname: "ARCH_DATA"
+    ]
+    repos.addMavenRepositories(repositories)
+    init.setupRepoOutAndBuildNumber()
+    init.configureSubProjects()
+    init.setupRelease()
+    init.enableDoclavaAndJDiff(this)
+    rootProject.tasks["generateDocs"].exclude '**/R.java'
+}
+
+
+// flatfoot docs
+def zipFlatfootDocsTask = rootProject.tasks.create(name : "createFlatfootDocsArchive", type : Zip) {
+    from rootProject.docsDir
+    destinationDir distDir
+    baseName = String.format("flatfoot-docs-%s", rootProject.ext.flatfoot.release_version)
+}
+
+buildServerAnchorTask.dependsOn zipFlatfootDocsTask
+zipFlatfootDocsTask.dependsOn rootProject.tasks["generateDocs"]
+
+// Disable API checks for now.
+checkApiRelease.enabled = false
+checkApi.enabled = false
+generateApi.enabled = false
+
+buildServerAnchorTask.dependsOn createArchive
+
+rootProject.ext.flatfootProjectGroups = [
+        "room" : "android.arch.persistence.room",
+        "lifecycle" : "android.arch.lifecycle",
+        "arch" : "android.arch.core",
+        "navigation" : "android.arch.navigation"]
+
+subprojects {
+    configurations.all {
+        resolutionStrategy {
+            force "com.google.guava:guava-jdk5:17.0"
+        }
+    }
+    repos.addMavenRepositories(project.repositories)
+    if (project.name == 'doclava' || project.name == 'jdiff') {
+        project.tasks.whenTaskAdded { task ->
+            if (task instanceof org.gradle.api.tasks.testing.Test) {
+                task.enabled = false
+            }
+        }
+        return
+    }
+
+    def mavenGroup = project.getPath().split(":")[1]
+    def finalGroup = rootProject.flatfootProjectGroups[mavenGroup]
+    if (finalGroup == null) {
+        return
+    }
+    project.group = finalGroup
+    project.version = flatfoot.release_version
+
+    if (project.getPath().contains("integration-tests")) {
+        // disable upload tasks
+        project.tasks.whenTaskAdded { task ->
+            if (task instanceof Upload || task.name == "generateSourceProps") {
+                task.enabled = false
+            }
+        }
+    } else {
+        // add license to pom
+        project.tasks.whenTaskAdded { task ->
+            if (task instanceof Upload) {
+                task.repositories {
+                    mavenDeployer {
+                        repository(url: project.uri(project.rootProject.supportRepoOut))
+                        pom.project {
+                            url "https://developer.android.com/topic/libraries/architecture/index.html"
+                            inceptionYear 2017
+                            licenses {
+                                license {
+                                    name 'The Apache Software License, Version 2.0'
+                                    url 'http://www.apache.org/licenses/LICENSE-2.0.txt'
+                                    distribution 'repo'
+                                }
+                            }
+                            scm {
+                                url "http://source.android.com"
+                                connection "scm:git:https://android.googlesource.com/platform/frameworks/support"
+                            }
+                            developers {
+                                developer {
+                                    name 'The Android Open Source Project'
+                                }
+                            }
+                        }
+                    }
+                }
+            }
+        }
+    }
+
+
+    project.plugins.whenPluginAdded { plugin ->
+        def isAndroidLibrary = "com.android.build.gradle.LibraryPlugin"
+                .equals(plugin.class.name)
+        def isJavaLibrary = "org.gradle.api.plugins.JavaPlugin".equals(plugin.class.name)
+        if (isAndroidLibrary) {
+            // it is an android lib, enable sources.
+            def sourcesTask = project.tasks.create(name: "sourcesJar", type : Jar) {
+                classifier = 'sources'
+                from android.sourceSets.main.getJava().getSrcDirs()
+            }
+            project.artifacts {
+                archives sourcesTask
+            }
+        } else if(isJavaLibrary && project.name == "common") {
+            // it is a shared lib, enable sources.
+            def sourcesTask = project.tasks.create(name: "sourcesJar", type : Jar) {
+                classifier = 'sources'
+                from sourceSets.main.allSource
+            }
+            project.artifacts {
+                archives sourcesTask
+            }
+        }
+    }
+
+    project.tasks.whenTaskAdded { task ->
+        if (task.name.startsWith("assembleAndroidTest")) {
+            buildServerAnchorTask.dependsOn task
+        }
+        if (task.name.startsWith("assembleDebug")) {
+            buildServerAnchorTask.dependsOn task
+        }
+    }
+
+    if (enablePublicRepos) {
+        project.afterEvaluate {
+            apply plugin: 'com.android.databinding.localizemaven'
+            project.localizeMaven {
+                localRepoDir = file("$prebuiltsRoot/tools/common/m2/repository")
+                otherRepoDirs = repoNames
+                licenseInformation = [
+                        [
+                                libraries: ["org.jetbrains:annotations",
+                                            "org.jetbrains.kotlin:kotlin-compiler-runner"],
+                                licenses : ["http://www.apache.org/licenses/LICENSE-2.0.txt"]
+                        ],
+                        [
+                                libraries: ["com.google.code.gson:gson"],
+                                licenses : ["http://www.apache.org/licenses/LICENSE-2.0.txt"]
+                        ]
+
+                ]
+            }
+        }
+    }
+}
+
+def createKotlinCheckstyle(Project project) {
+    def fs = files();
+    fs += files(project.sourceSets.main.allJava.srcDirs.collect{fileTree(it)})
+    fs += files(project.sourceSets.test.allJava.srcDirs.collect{fileTree(it)})
+    fs = fs.filter{file -> file.name.endsWith(".kt")}
+    def kotlinCheckstyle = createCheckstyleTask(project, 'checkstyleKotlin',
+            "${project.rootProject.ext.supportRootFolder}/app-toolkit/kotlin-checkstyle.xml",
+            fs.files)
+
+    project.tasks.findByName("check").dependsOn(kotlinCheckstyle)
+    // poor man's line length check
+    def lineCheck = project.tasks.create(name : "lineLengthCheck") {
+        (project.sourceSets.main.allJava.getSourceDirectories() +
+            project.sourceSets.test.allJava.getSourceDirectories()).each { sourceDir ->
+                  fileTree(dir : sourceDir, include : "**/*.kt").each{ file ->
+                      file.readLines().eachWithIndex { line, index ->
+                          if (line.size() > 100) {
+                              project.logger.error("line too long: file: $file" +
+                                      " index:$index line: $line")
+                          }
+                      }
+                  }
+        }
+    }
+    kotlinCheckstyle.dependsOn(lineCheck)
+}
+
+def createAndroidCheckstyle(Project project) {
+    def fs = files()
+    if (project.hasProperty('android')) {
+        fs += files(project.android.sourceSets.main.java.getSrcDirs().collect {fileTree(it)})
+    }
+    if (project.sourceSets.hasProperty('main')) {
+        fs += files(project.sourceSets.main.allJava)
+    }
+    fs = fs.filter{file -> file.name.endsWith(".java")}
+
+    def checkStyle = createCheckstyleTask(project, 'checkstyleAndroid',
+            "${project.rootProject.ext.checkoutRoot}/prebuilts/checkstyle/android-style.xml",
+            fs.files)
+    project.tasks.findByName("check").dependsOn(checkStyle)
+}
+
+def createCheckstyleTask(project, taskName, configFile, inputFiles) {
+    def arguments = ['-c', configFile]
+    arguments.addAll(inputFiles)
+    def checkStyle = project.tasks.create(name : taskName, type: JavaExec) {
+        inputs.files(inputFiles).skipWhenEmpty()
+        main = "com.puppycrawl.tools.checkstyle.Main"
+        args = arguments
+        classpath = files(file("${project.rootProject.ext.checkoutRoot}/prebuilts/checkstyle/checkstyle.jar").path)
+    }
+    return checkStyle;
+}
+
+ext.createKotlinCheckstyle = this.&createKotlinCheckstyle
+ext.createAndroidCheckstyle = this.&createAndroidCheckstyle
diff --git a/app-toolkit/kotlin-checkstyle.xml b/app-toolkit/kotlin-checkstyle.xml
new file mode 100644
index 0000000..5cdad0a
--- /dev/null
+++ b/app-toolkit/kotlin-checkstyle.xml
@@ -0,0 +1,51 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+  ~ Copyright (C) 2016 The Android Open Source Project
+  ~
+  ~ Licensed under the Apache License, Version 2.0 (the "License");
+  ~ you may not use this file except in compliance with the License.
+  ~ You may obtain a copy of the License at
+  ~
+  ~      http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing, software
+  ~ distributed under the License is distributed on an "AS IS" BASIS,
+  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  ~ See the License for the specific language governing permissions and
+  ~ limitations under the License.
+  -->
+
+<!DOCTYPE module PUBLIC "-//Puppy Crawl//DTD Check Configuration 1.3//EN"
+    "http://www.puppycrawl.com/dtds/configuration_1_3.dtd">
+<!-- this is a very limited set of checkstyle rules mainly because TreeWalker rules are not
+supported for kt files yet.-->
+<module name="Checker">
+    <property name="severity" value="warning"/>
+    <property name="charset" value="UTF-8"/>
+    <module name="SuppressionCommentFilter">
+        <property name="offCommentFormat" value="CHECKSTYLE:OFF IndentationCheck"/>
+        <property name="onCommentFormat" value="CHECKSTYLE:ON IndentationCheck"/>
+        <property name="checkFormat" value="IndentationCheck"/>
+    </module>
+    <module name="SuppressionCommentFilter">
+        <property name="offCommentFormat" value="CHECKSTYLE:OFF Generated code"/>
+        <property name="onCommentFormat" value="CHECKSTYLE:ON Generated code"/>
+    </module>
+    <module name="FileTabCharacter">
+        <property name="severity" value="error"/>
+    </module>
+    <module name="NewlineAtEndOfFile">
+        <property name="severity" value="error"/>
+    </module>
+    <module name="RegexpSingleline">
+        <property name="severity" value="error"/>
+        <property name="format" value="[ \t]+$"/>
+        <property name="message" value="Trailing whitespace"/>
+    </module>
+    <module name="RegexpHeader">
+        <property name="severity" value="error"/>
+        <message key="header.mismatch"
+                 value="Android Copyright header seems to be incorrect. Expected ''{0}'' on this line."/>
+        <property name="header" value="^/\*\n \* Copyright \([Cc]\) [0-9]{4} The Android Open Source Project\n \*\n \* Licensed under the Apache License, Version 2\.0 \(the \&quot;License\&quot;\);\n \* you may not use this file except in compliance with the License.\n \* You may obtain a copy of the License at\n \*\n \*      http:\/\/www\.apache\.org\/licenses\/LICENSE-2\.0\n \*\n \* Unless required by applicable law or agreed to in writing, software\n \* distributed under the License is distributed on an &quot;AS IS&quot; BASIS,\n \* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied\.\n \* See the License for the specific language governing permissions and\n \* limitations under the License\.\n \*\/" />
+    </module>
+</module>
diff --git a/app-toolkit/localize.sh b/app-toolkit/localize.sh
new file mode 100755
index 0000000..3b7cb99
--- /dev/null
+++ b/app-toolkit/localize.sh
@@ -0,0 +1,2 @@
+#!/bin/bash
+ALLOW_PUBLIC_REPOS=true ./gradlew localizeDependencies :room:compiler:buildLicenseNotice :lifecycle:compiler:buildLicenseNotice
\ No newline at end of file
diff --git a/app-toolkit/runtime/build.gradle b/app-toolkit/runtime/build.gradle
new file mode 100644
index 0000000..5e2fe46
--- /dev/null
+++ b/app-toolkit/runtime/build.gradle
@@ -0,0 +1,71 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT 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 com.android.builder.core.BuilderConstants
+apply plugin: 'com.android.library'
+apply plugin: 'maven'
+
+android {
+    compileSdkVersion tools.current_sdk
+    buildToolsVersion tools.build_tools_version
+
+    defaultConfig {
+        minSdkVersion flatfoot.min_sdk
+        targetSdkVersion tools.current_sdk
+        versionCode 1
+        versionName "1.0"
+
+        testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
+
+    }
+    buildTypes {
+        release {
+            minifyEnabled false
+            proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
+        }
+    }
+
+    testOptions {
+        unitTests.returnDefaultValues = true
+    }
+    compileOptions {
+        sourceCompatibility JavaVersion.VERSION_1_7
+        targetCompatibility JavaVersion.VERSION_1_7
+    }
+}
+
+dependencies {
+    compile libs.support.annotations
+    compile project(":arch:common")
+    testCompile libs.junit
+    testCompile libs.mockito_core
+    testCompile(libs.test_runner) {
+        exclude module: 'support-annotations'
+    }
+}
+
+archivesBaseName = "runtime"
+
+createAndroidCheckstyle(project)
+
+android.libraryVariants.all { variant ->
+    def name = variant.buildType.name
+    def suffix = name.capitalize()
+    def jarTask = project.tasks.create(name: "jar${suffix}", type: Jar){
+        dependsOn variant.javaCompile
+        from variant.javaCompile.destinationDir
+        destinationDir new File(project.buildDir, "libJar")
+    }
+}
diff --git a/app-toolkit/runtime/src/main/AndroidManifest.xml b/app-toolkit/runtime/src/main/AndroidManifest.xml
new file mode 100644
index 0000000..3f40068
--- /dev/null
+++ b/app-toolkit/runtime/src/main/AndroidManifest.xml
@@ -0,0 +1,19 @@
+<!--
+  ~ Copyright (C) 2017 The Android Open Source Project
+  ~
+  ~ Licensed under the Apache License, Version 2.0 (the "License");
+  ~ you may not use this file except in compliance with the License.
+  ~ You may obtain a copy of the License at
+  ~
+  ~      http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing, software
+  ~ distributed under the License is distributed on an "AS IS" BASIS,
+  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  ~ See the License for the specific language governing permissions and
+  ~ limitations under the License.
+  -->
+
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+          package="android.arch.core">
+</manifest>
diff --git a/app-toolkit/runtime/src/main/java/android/arch/core/executor/AppToolkitTaskExecutor.java b/app-toolkit/runtime/src/main/java/android/arch/core/executor/AppToolkitTaskExecutor.java
new file mode 100644
index 0000000..7337f74
--- /dev/null
+++ b/app-toolkit/runtime/src/main/java/android/arch/core/executor/AppToolkitTaskExecutor.java
@@ -0,0 +1,117 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.arch.core.executor;
+
+import android.support.annotation.NonNull;
+import android.support.annotation.Nullable;
+import android.support.annotation.RestrictTo;
+
+import java.util.concurrent.Executor;
+
+/**
+ * A static class that serves as a central point to execute common tasks.
+ * <p>
+ *
+ * @hide This API is not final.
+ */
+@RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
+public class AppToolkitTaskExecutor extends TaskExecutor {
+    private static volatile AppToolkitTaskExecutor sInstance;
+
+    @NonNull
+    private TaskExecutor mDelegate;
+
+    @NonNull
+    private TaskExecutor mDefaultTaskExecutor;
+
+    @NonNull
+    private static final Executor sMainThreadExecutor = new Executor() {
+        @Override
+        public void execute(Runnable command) {
+            getInstance().postToMainThread(command);
+        }
+    };
+
+    @NonNull
+    private static final Executor sIOThreadExecutor = new Executor() {
+        @Override
+        public void execute(Runnable command) {
+            getInstance().executeOnDiskIO(command);
+        }
+    };
+
+    private AppToolkitTaskExecutor() {
+        mDefaultTaskExecutor = new DefaultTaskExecutor();
+        mDelegate = mDefaultTaskExecutor;
+    }
+
+    /**
+     * Returns an instance of the task executor.
+     *
+     * @return The singleton AppToolkitTaskExecutor.
+     */
+    public static AppToolkitTaskExecutor getInstance() {
+        if (sInstance != null) {
+            return sInstance;
+        }
+        synchronized (AppToolkitTaskExecutor.class) {
+            if (sInstance == null) {
+                sInstance = new AppToolkitTaskExecutor();
+            }
+        }
+        return sInstance;
+    }
+
+    /**
+     * Sets a delegate to handle task execution requests.
+     * <p>
+     * If you have a common executor, you can set it as the delegate and App Toolkit components will
+     * use your executors. You may also want to use this for your tests.
+     * <p>
+     * Calling this method with {@code null} sets it to the default TaskExecutor.
+     *
+     * @param taskExecutor The task executor to handle task requests.
+     */
+    public void setDelegate(@Nullable TaskExecutor taskExecutor) {
+        mDelegate = taskExecutor == null ? mDefaultTaskExecutor : taskExecutor;
+    }
+
+    @Override
+    public void executeOnDiskIO(Runnable runnable) {
+        mDelegate.executeOnDiskIO(runnable);
+    }
+
+    @Override
+    public void postToMainThread(Runnable runnable) {
+        mDelegate.postToMainThread(runnable);
+    }
+
+    @NonNull
+    public static Executor getMainThreadExecutor() {
+        return sMainThreadExecutor;
+    }
+
+    @NonNull
+    public static Executor getIOThreadExecutor() {
+        return sIOThreadExecutor;
+    }
+
+    @Override
+    public boolean isMainThread() {
+        return mDelegate.isMainThread();
+    }
+}
diff --git a/app-toolkit/runtime/src/main/java/android/arch/core/executor/DefaultTaskExecutor.java b/app-toolkit/runtime/src/main/java/android/arch/core/executor/DefaultTaskExecutor.java
new file mode 100644
index 0000000..dbb1054
--- /dev/null
+++ b/app-toolkit/runtime/src/main/java/android/arch/core/executor/DefaultTaskExecutor.java
@@ -0,0 +1,60 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.arch.core.executor;
+
+import android.os.Handler;
+import android.os.Looper;
+import android.support.annotation.Nullable;
+import android.support.annotation.RestrictTo;
+
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.Executors;
+
+/**
+ * @hide
+ */
+@RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
+public class DefaultTaskExecutor extends TaskExecutor {
+    private final Object mLock = new Object();
+    private ExecutorService mDiskIO = Executors.newFixedThreadPool(2);
+
+    @Nullable
+    private volatile Handler mMainHandler;
+
+    @Override
+    public void executeOnDiskIO(Runnable runnable) {
+        mDiskIO.execute(runnable);
+    }
+
+    @Override
+    public void postToMainThread(Runnable runnable) {
+        if (mMainHandler == null) {
+            synchronized (mLock) {
+                if (mMainHandler == null) {
+                    mMainHandler = new Handler(Looper.getMainLooper());
+                }
+            }
+        }
+        //noinspection ConstantConditions
+        mMainHandler.post(runnable);
+    }
+
+    @Override
+    public boolean isMainThread() {
+        return Looper.getMainLooper().getThread() == Thread.currentThread();
+    }
+}
diff --git a/app-toolkit/runtime/src/main/java/android/arch/core/executor/TaskExecutor.java b/app-toolkit/runtime/src/main/java/android/arch/core/executor/TaskExecutor.java
new file mode 100644
index 0000000..055b476
--- /dev/null
+++ b/app-toolkit/runtime/src/main/java/android/arch/core/executor/TaskExecutor.java
@@ -0,0 +1,66 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.arch.core.executor;
+
+import android.support.annotation.RestrictTo;
+
+/**
+ * A task executor that can divide tasks into logical groups.
+ * <p>
+ * It holds a collection a executors for each group of task.
+ * <p>
+ * TODO: Don't use this from outside, we don't know what the API will look like yet.
+ * @hide
+ */
+@RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
+public abstract class TaskExecutor {
+    /**
+     * Executes the given task in the disk IO thread pool.
+     *
+     * @param runnable The runnable to run in the disk IO thread pool.
+     */
+    public abstract void executeOnDiskIO(Runnable runnable);
+
+    /**
+     * Posts the given task to the main thread.
+     *
+     * @param runnable The runnable to run on the main thread.
+     */
+    public abstract void postToMainThread(Runnable runnable);
+
+    /**
+     * Executes the given task on the main thread.
+     * <p>
+     * If the current thread is a main thread, immediately runs the given runnable.
+     *
+     * @param runnable The runnable to run on the main thread.
+     */
+    public void executeOnMainThread(Runnable runnable) {
+        if (isMainThread()) {
+            runnable.run();
+        } else {
+            postToMainThread(runnable);
+        }
+    }
+
+    /**
+     * Returns true if the current thread is the main thread, false otherwise.
+     *
+     * @return true if we are on the main thread, false otherwise.
+     */
+    public abstract boolean isMainThread();
+}
diff --git a/app-toolkit/settings.gradle b/app-toolkit/settings.gradle
new file mode 100644
index 0000000..15ed47c
--- /dev/null
+++ b/app-toolkit/settings.gradle
@@ -0,0 +1,106 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+// If you change this file, you should also change the settings gradle inside
+// the sub project.
+
+def inAppToolkitProject = rootProject.name == "app-toolkit"
+def supportRoot
+if (inAppToolkitProject) {
+    supportRoot = new File(rootProject.projectDir, "..").getCanonicalFile()
+} else {
+    supportRoot = rootProject.projectDir
+}
+
+println "support root:${supportRoot}"
+
+include ':arch:runtime'
+project(':arch:runtime').projectDir = new File(supportRoot, "app-toolkit/runtime")
+
+include ':arch:common'
+project(':arch:common').projectDir = new File(supportRoot, "app-toolkit/common")
+
+include ':arch:core-testing'
+project(':arch:core-testing').projectDir = new File(supportRoot, "app-toolkit/core-testing")
+
+include ':lifecycle:extensions'
+project(':lifecycle:extensions').projectDir = new File(supportRoot, "lifecycle/extensions")
+
+include ':lifecycle:reactivestreams'
+project(':lifecycle:reactivestreams').projectDir = new File(supportRoot, "lifecycle/reactivestreams")
+
+include ':lifecycle:runtime'
+project(':lifecycle:runtime').projectDir = new File(supportRoot, "lifecycle/runtime")
+
+include ':lifecycle:common'
+project(':lifecycle:common').projectDir = new File(supportRoot, "lifecycle/common")
+
+include ':lifecycle:compiler'
+project(':lifecycle:compiler').projectDir = new File(supportRoot, "lifecycle/compiler")
+
+include ':lifecycle:integration-tests:testapp'
+project(':lifecycle:integration-tests:testapp').projectDir = new File(supportRoot, "lifecycle/integration-tests/testapp")
+
+include ':room:common'
+project(':room:common').projectDir = new File(supportRoot, "room/common")
+
+include ':room:runtime'
+project(':room:runtime').projectDir = new File(supportRoot, "room/runtime")
+
+include ':room:compiler'
+project(':room:compiler').projectDir = new File(supportRoot, "room/compiler")
+
+include ':room:migration'
+project(':room:migration').projectDir = new File(supportRoot, "room/migration")
+
+include ':room:db'
+project(':room:db').projectDir = new File(supportRoot, "room/db")
+
+include ":room:db-impl"
+project(':room:db-impl').projectDir = new File(supportRoot, "room/db-impl")
+
+include ":room:testing"
+project(':room:testing').projectDir = new File(supportRoot, "room/testing")
+
+include ":room:rxjava2"
+project(':room:rxjava2').projectDir = new File(supportRoot, "room/rxjava2")
+
+include ':room:integration-tests:testapp'
+project(':room:integration-tests:testapp').projectDir = new File(supportRoot, "room/integration-tests/testapp")
+
+/////////////////////////////
+//
+// SupportLib
+//
+/////////////////////////////
+if (inAppToolkitProject && System.getenv("USE_SUPPORT_LIB_SOURCE")) {
+    apply from: "${supportRoot.absolutePath}/app-toolkit/settings_support_lib.gradle"
+}
+
+/////////////////////////////
+//
+// External
+//
+/////////////////////////////
+if (inAppToolkitProject) {
+    File externalRoot = new File(supportRoot, '../../external')
+
+    include ':doclava'
+    project(':doclava').projectDir = new File(externalRoot, 'doclava')
+
+    include ':jdiff'
+    project(':jdiff').projectDir = new File(externalRoot, 'jdiff')
+}
diff --git a/app-toolkit/settings_support_lib.gradle b/app-toolkit/settings_support_lib.gradle
new file mode 100644
index 0000000..c6f56f1
--- /dev/null
+++ b/app-toolkit/settings_support_lib.gradle
@@ -0,0 +1,36 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+// settings file to include support lib sources.
+def supportRootDir = new File("../.")
+
+include ':support-core-utils'
+project(':support-core-utils').projectDir = new File(supportRootDir, 'core-utils')
+
+include ':support-fragment'
+project(':support-fragment').projectDir = new File(supportRootDir, 'fragment')
+
+include ':support-compat'
+project(':support-compat').projectDir = new File(supportRootDir, 'compat')
+
+include ':support-annotations'
+project(':support-annotations').projectDir = new File(supportRootDir, 'annotations')
+
+include ':support-core-ui'
+project(':support-core-ui').projectDir = new File(supportRootDir, 'core-ui')
+
+include ':support-appcompat-v7'
+project(':support-appcompat-v7').projectDir = new File(rootDir, 'v7/appcompat')
\ No newline at end of file
diff --git a/buildSrc/dependencies.gradle b/buildSrc/dependencies.gradle
index 5cf3ae6..7c001b0 100644
--- a/buildSrc/dependencies.gradle
+++ b/buildSrc/dependencies.gradle
@@ -25,6 +25,7 @@
 libs.espresso_core = 'com.android.support.test.espresso:espresso-core:2.3-alpha'
 libs.espresso_contrib = 'com.android.support.test.espresso:espresso-contrib:2.3-alpha'
 libs.jacoco = 'org.jacoco:org.jacoco.core:0.7.8'
+libs.test_rules = 'com.android.support.test:rules:0.6-alpha'
 
 def androidPluginVersionOverride = System.getenv("GRADLE_PLUGIN_VERSION")
 
@@ -32,7 +33,7 @@
     libs.gradle = 'com.android.tools.build:gradle:' + androidPluginVersionOverride
 } else {
     // Keep gradle plugin version in sync with ub_supportlib-master manifest.
-    libs.gradle = 'com.android.tools.build:gradle:2.4.0-alpha6'
+    libs.gradle = 'com.android.tools.build:gradle:3.0.0-alpha6'
 }
 
 // Other dependencies
diff --git a/buildSrc/diff_and_docs.gradle b/buildSrc/diff_and_docs.gradle
index 47fcefc..61a4481 100644
--- a/buildSrc/diff_and_docs.gradle
+++ b/buildSrc/diff_and_docs.gradle
@@ -440,7 +440,8 @@
                 fileTree(releaseVariant.aidlCompile.sourceOutputDir) +
                 fileTree(releaseVariant.outputs[0].processResources.sourceOutputDir)
     }
-    task.classpath += files{releaseVariant.javaCompile.classpath.files} +
+
+    task.classpath += releaseVariant.getCompileClasspath(null) +
             files(releaseVariant.javaCompile.destinationDir)
 }
 
diff --git a/buildSrc/init.gradle b/buildSrc/init.gradle
index 4e66dc8..a483337 100644
--- a/buildSrc/init.gradle
+++ b/buildSrc/init.gradle
@@ -255,13 +255,6 @@
                         }
                     }
 
-                    uploadTask.repositories.mavenDeployer.pom*.whenConfigured { pom ->
-                        pom.dependencies.findAll { dep ->
-                            dep.groupId == 'com.android.support' &&
-                                    dep.artifactId != 'support-annotations'
-                        }*.type = 'aar'
-                    }
-
                     // Before the upload, make sure the repo is ready.
                     uploadTask.dependsOn rootProject.tasks.prepareRepo
 
@@ -281,9 +274,10 @@
                 task.doLast {
                     def source = testApk ? project.android.sourceSets.androidTest
                             : project.android.sourceSets.main
-                    if (task.hasProperty("outputFile") && !source.java.sourceFiles.isEmpty()) {
+                    if (task.hasProperty("outputDirectory") && !source.java.sourceFiles.isEmpty()) {
                         copy {
-                            from(task.outputFile)
+                            from(task.outputDirectory)
+                            include '*.apk'
                             into(rootProject.ext.testApkDistOut)
                             rename { String fileName ->
                                 // multiple modules may have the same name so prefix the name with
@@ -360,4 +354,4 @@
 ext.init.setupRelease = this.&setupRelease
 ext.init.loadDefaultVersions = this.&loadDefaultVersions
 ext.init.configureSubProjects = this.&configureSubProjects
-ext.init.configureBuildOnServer = this.&configureBuildOnServer
\ No newline at end of file
+ext.init.configureBuildOnServer = this.&configureBuildOnServer
diff --git a/buildSrc/repos.gradle b/buildSrc/repos.gradle
index 6ee60e1..caedf3a 100644
--- a/buildSrc/repos.gradle
+++ b/buildSrc/repos.gradle
@@ -44,6 +44,7 @@
     if (System.getenv("ALLOW_PUBLIC_REPOS") != null) {
         handler.mavenCentral()
         handler.jcenter()
+        handler.google()
     }
     def androidPluginRepoOverride = System.getenv("GRADLE_PLUGIN_REPO")
     if (androidPluginRepoOverride != null) {
diff --git a/compat/api21/android/support/v4/graphics/drawable/DrawableWrapperApi21.java b/compat/api21/android/support/v4/graphics/drawable/DrawableWrapperApi21.java
index 6c7f74b..5195cc9 100644
--- a/compat/api21/android/support/v4/graphics/drawable/DrawableWrapperApi21.java
+++ b/compat/api21/android/support/v4/graphics/drawable/DrawableWrapperApi21.java
@@ -16,6 +16,8 @@
 
 package android.support.v4.graphics.drawable;
 
+import static android.support.annotation.RestrictTo.Scope.LIBRARY_GROUP;
+
 import android.content.res.ColorStateList;
 import android.content.res.Resources;
 import android.graphics.Outline;
@@ -25,20 +27,29 @@
 import android.graphics.drawable.DrawableContainer;
 import android.graphics.drawable.GradientDrawable;
 import android.graphics.drawable.InsetDrawable;
+import android.graphics.drawable.RippleDrawable;
 import android.os.Build;
 import android.support.annotation.NonNull;
 import android.support.annotation.Nullable;
 import android.support.annotation.RequiresApi;
+import android.support.annotation.RestrictTo;
+import android.util.Log;
+
+import java.lang.reflect.Method;
 
 @RequiresApi(21)
 class DrawableWrapperApi21 extends DrawableWrapperApi19 {
+    private static final String TAG = "DrawableWrapperApi21";
+    private static Method sIsProjectedDrawableMethod;
 
     DrawableWrapperApi21(Drawable drawable) {
         super(drawable);
+        findAndCacheIsProjectedDrawableMethod();
     }
 
     DrawableWrapperApi21(DrawableWrapperState state, Resources resources) {
         super(state, resources);
+        findAndCacheIsProjectedDrawableMethod();
     }
 
     @Override
@@ -103,12 +114,30 @@
     protected boolean isCompatTintEnabled() {
         if (Build.VERSION.SDK_INT == 21) {
             final Drawable drawable = mDrawable;
-            return drawable instanceof GradientDrawable || drawable instanceof DrawableContainer
-                    || drawable instanceof InsetDrawable;
+            return drawable instanceof GradientDrawable
+                    || drawable instanceof DrawableContainer
+                    || drawable instanceof InsetDrawable
+                    || drawable instanceof RippleDrawable;
         }
         return false;
     }
 
+    /**
+     * @hide
+     */
+    @RestrictTo(LIBRARY_GROUP)
+    public boolean isProjected() {
+        if (mDrawable != null && sIsProjectedDrawableMethod != null) {
+            try {
+                return (Boolean) sIsProjectedDrawableMethod.invoke(mDrawable);
+            } catch (Exception ex) {
+                Log.w(TAG, "Error calling Drawable#isProjected() method", ex);
+            }
+        }
+
+        return false;
+    }
+
     @NonNull
     @Override
     DrawableWrapperState mutateConstantState() {
@@ -126,4 +155,14 @@
             return new DrawableWrapperApi21(this, res);
         }
     }
+
+    private void findAndCacheIsProjectedDrawableMethod() {
+        if (sIsProjectedDrawableMethod == null) {
+            try {
+                sIsProjectedDrawableMethod = Drawable.class.getDeclaredMethod("isProjected");
+            } catch (Exception ex) {
+                Log.w(TAG, "Failed to retrieve Drawable#isProjected() method", ex);
+            }
+        }
+    }
 }
diff --git a/compat/ics/android/support/v4/graphics/PaintCompatApi14.java b/compat/ics/android/support/v4/graphics/PaintCompatApi14.java
index 86e87d8..7a7de7c 100644
--- a/compat/ics/android/support/v4/graphics/PaintCompatApi14.java
+++ b/compat/ics/android/support/v4/graphics/PaintCompatApi14.java
@@ -24,6 +24,7 @@
 class PaintCompatApi14 {
     // U+DFFFD which is very end of unassigned plane.
     private static final String TOFU_STRING = "\uDB3F\uDFFD";
+    private static final String EM_STRING = "m";
 
     private static final ThreadLocal<Pair<Rect, Rect>> sRectThreadLocal = new ThreadLocal<>();
 
@@ -36,6 +37,8 @@
         }
 
         final float missingGlyphWidth = paint.measureText(TOFU_STRING);
+        final float emGlyphWidth = paint.measureText(EM_STRING);
+
         final float width = paint.measureText(string);
 
         if (width == 0f) {
@@ -46,7 +49,7 @@
         if (string.codePointCount(0, string.length()) > 1) {
             // Heuristic to detect fallback glyphs for ligatures like flags and ZWJ sequences
             // Return false if string is rendered too widely
-            if (width > 2 * missingGlyphWidth) {
+            if (width > 2 * emGlyphWidth) {
                 return false;
             }
 
diff --git a/compat/java/android/support/v4/app/JobIntentService.java b/compat/java/android/support/v4/app/JobIntentService.java
index b08c654..4c83eb9 100644
--- a/compat/java/android/support/v4/app/JobIntentService.java
+++ b/compat/java/android/support/v4/app/JobIntentService.java
@@ -309,6 +309,7 @@
                 work = mParams.dequeueWork();
             }
             if (work != null) {
+                work.getIntent().setExtrasClassLoader(mService.getClassLoader());
                 return new WrapperWorkItem(work);
             } else {
                 return null;
@@ -393,13 +394,13 @@
         }
 
         @Override
+        protected void onCancelled(Void aVoid) {
+            processorFinished();
+        }
+
+        @Override
         protected void onPostExecute(Void aVoid) {
-            if (mCompatQueue != null) {
-                synchronized (mCompatQueue) {
-                    mCurProcessor = null;
-                    checkForMoreCompatWorkLocked();
-                }
-            }
+            processorFinished();
         }
     }
 
@@ -602,11 +603,22 @@
         }
     }
 
-    void checkForMoreCompatWorkLocked() {
-        // The async task has finished, but we may have gotten more work scheduled in the
-        // meantime.  If so,
-        if (mCompatQueue != null && mCompatQueue.size() > 0) {
-            ensureProcessorRunningLocked();
+    void processorFinished() {
+        if (mCompatQueue != null) {
+            synchronized (mCompatQueue) {
+                mCurProcessor = null;
+                // The async task has finished, but we may have gotten more work scheduled in the
+                // meantime.  If so, we need to restart the new processor to execute it.  If there
+                // is no more work at this point, either the service is in the process of being
+                // destroyed (because we called stopSelf on the last intent started for it), or
+                // someone has already called startService with a new Intent that will be
+                // arriving shortly.  In either case, we want to just leave the service
+                // waiting -- either to get destroyed, or get a new onStartCommand() callback
+                // which will then kick off a new processor.
+                if (mCompatQueue != null && mCompatQueue.size() > 0) {
+                    ensureProcessorRunningLocked();
+                }
+            }
         }
     }
 
diff --git a/compat/java/android/support/v4/app/NotificationCompat.java b/compat/java/android/support/v4/app/NotificationCompat.java
index 89f0e81..10da04a 100644
--- a/compat/java/android/support/v4/app/NotificationCompat.java
+++ b/compat/java/android/support/v4/app/NotificationCompat.java
@@ -604,10 +604,27 @@
     @RestrictTo(LIBRARY_GROUP)
     protected static class BuilderExtender {
         public Notification build(Builder b, NotificationBuilderWithBuilderAccessor builder) {
+            RemoteViews styleContentView = b.mStyle != null
+                    ? b.mStyle.makeContentView(builder)
+                    : null;
             Notification n = builder.build();
-            if (b.mContentView != null) {
+            if (styleContentView != null) {
+                n.contentView = styleContentView;
+            } else if (b.mContentView != null) {
                 n.contentView = b.mContentView;
             }
+            if (Build.VERSION.SDK_INT >= 16 && b.mStyle != null) {
+                RemoteViews styleBigContentView = b.mStyle.makeBigContentView(builder);
+                if (styleBigContentView != null) {
+                    n.bigContentView = styleBigContentView;
+                }
+            }
+            if (Build.VERSION.SDK_INT >= 21 && b.mStyle != null) {
+                RemoteViews styleHeadsUpContentView = b.mStyle.makeHeadsUpContentView(builder);
+                if (styleHeadsUpContentView != null) {
+                    n.headsUpContentView = styleHeadsUpContentView;
+                }
+            }
             return n;
         }
     }
@@ -1757,12 +1774,6 @@
             return this;
         }
 
-        /** @deprecated removed from API 26 */
-        @Deprecated
-        public Builder setChannel(@NonNull String channelId) {
-            return setChannelId(channelId);
-        }
-
         /**
          * Specifies the time at which this notification should be canceled, if it is not already
          * canceled.
@@ -1772,12 +1783,6 @@
             return this;
         }
 
-        /** @deprecated removed from API 26 */
-        @Deprecated
-        public Builder setTimeout(long durationMs) {
-            return setTimeoutAfter(durationMs);
-        }
-
         /**
          * If this notification is duplicative of a Launcher shortcut, sets the
          * {@link android.support.v4.content.pm.ShortcutInfoCompat#getId() id} of the shortcut, in
@@ -1925,7 +1930,11 @@
      * effect.
      */
     public static abstract class Style {
-        Builder mBuilder;
+        /**
+         * @hide
+         */
+        @RestrictTo(LIBRARY_GROUP)
+        protected Builder mBuilder;
         CharSequence mBigContentTitle;
         CharSequence mSummaryText;
         boolean mSummaryTextSet = false;
@@ -1959,6 +1968,30 @@
          * @hide
          */
         @RestrictTo(LIBRARY_GROUP)
+        public RemoteViews makeContentView(NotificationBuilderWithBuilderAccessor builder) {
+            return null;
+        }
+
+        /**
+         * @hide
+         */
+        @RestrictTo(LIBRARY_GROUP)
+        public RemoteViews makeBigContentView(NotificationBuilderWithBuilderAccessor builder) {
+            return null;
+        }
+
+        /**
+         * @hide
+         */
+        @RestrictTo(LIBRARY_GROUP)
+        public RemoteViews makeHeadsUpContentView(NotificationBuilderWithBuilderAccessor builder) {
+            return null;
+        }
+
+        /**
+         * @hide
+         */
+        @RestrictTo(LIBRARY_GROUP)
         // TODO: implement for all styles
         public void addCompatExtras(Bundle extras) {
         }
@@ -2318,6 +2351,26 @@
                             ? makeMessageLine(latestIncomingMessage)
                             : latestIncomingMessage.getText());
                 }
+                // Build a fallback BigTextStyle for API 16-23 devices
+                if (Build.VERSION.SDK_INT >= 16) {
+                    SpannableStringBuilder completeMessage = new SpannableStringBuilder();
+                    boolean showNames = mConversationTitle != null
+                            || hasMessagesWithoutSender();
+                    for (int i = mMessages.size() - 1; i >= 0; i--) {
+                        MessagingStyle.Message message = mMessages.get(i);
+                        CharSequence line;
+                        line = showNames ? makeMessageLine(message) : message.getText();
+                        if (i != mMessages.size() - 1) {
+                            completeMessage.insert(0, "\n");
+                        }
+                        completeMessage.insert(0, line);
+                    }
+                    NotificationCompatJellybean.addBigTextStyle(builder,
+                            null,
+                            false,
+                            null,
+                            completeMessage);
+                }
             }
         }
 
@@ -2337,6 +2390,16 @@
             return null;
         }
 
+        private boolean hasMessagesWithoutSender() {
+            for (int i = mMessages.size() - 1; i >= 0; i--) {
+                MessagingStyle.Message message = mMessages.get(i);
+                if (message.getSender() == null) {
+                    return true;
+                }
+            }
+            return false;
+        }
+
         private CharSequence makeMessageLine(MessagingStyle.Message message) {
             BidiFormatter bidi = BidiFormatter.getInstance();
             SpannableStringBuilder sb = new SpannableStringBuilder();
@@ -4513,12 +4576,6 @@
         }
     }
 
-    /** @deprecated removed from API 26 */
-    @Deprecated
-    public static String getChannel(Notification notification) {
-        return getChannelId(notification);
-    }
-
     /**
      * Returns the time at which this notification should be canceled by the system, if it's not
      * canceled already.
@@ -4531,12 +4588,6 @@
         }
     }
 
-    /** @deprecated removed from API 26 */
-    @Deprecated
-    public static long getTimeout(Notification notification) {
-        return getTimeoutAfter(notification);
-    }
-
     /**
      * Returns what icon should be shown for this notification if it is being displayed in a
      * Launcher that supports badging. Will be one of {@link #BADGE_ICON_NONE},
diff --git a/compat/java/android/support/v4/util/ObjectsCompat.java b/compat/java/android/support/v4/util/ObjectsCompat.java
new file mode 100644
index 0000000..4a4c844
--- /dev/null
+++ b/compat/java/android/support/v4/util/ObjectsCompat.java
@@ -0,0 +1,74 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package android.support.v4.util;
+
+import android.os.Build;
+import android.support.annotation.RequiresApi;
+
+import java.util.Objects;
+
+/**
+ * This class consists of static utility methods for operating on objects.
+ */
+public class ObjectsCompat {
+    private static final ImplBase IMPL;
+
+    static {
+        if (Build.VERSION.SDK_INT >= 19) {
+            IMPL = new ImplApi19();
+        } else {
+            IMPL = new ImplBase();
+        }
+    }
+
+    private ObjectsCompat() {
+        // Non-instantiable.
+    }
+
+    /**
+     * Returns {@code true} if the arguments are equal to each other
+     * and {@code false} otherwise.
+     * <p>
+     * Consequently, if both arguments are {@code null}, {@code true}
+     * is returned and if exactly one argument is {@code null}, {@code
+     * false} is returned. Otherwise, equality is determined by using
+     * the {@link Object#equals equals} method of the first
+     * argument.
+     *
+     * @param a an object
+     * @param b an object to be compared with {@code a} for equality
+     * @return {@code true} if the arguments are equal to each other
+     *         and {@code false} otherwise
+     * @see Object#equals(Object)
+     */
+    public static boolean equals(Object a, Object b) {
+        return IMPL.equals(a, b);
+    }
+
+    private static class ImplBase {
+        public boolean equals(Object a, Object b) {
+            return (a == b) || (a != null && a.equals(b));
+        }
+    }
+
+    @RequiresApi(19)
+    private static class ImplApi19 extends ImplBase {
+        @Override
+        public boolean equals(Object a, Object b) {
+            return Objects.equals(a, b);
+        }
+    }
+}
diff --git a/compat/lint-baseline.xml b/compat/lint-baseline.xml
index 172bbf6..fb44511 100644
--- a/compat/lint-baseline.xml
+++ b/compat/lint-baseline.xml
@@ -1,4 +1,4 @@
 <?xml version="1.0" encoding="UTF-8"?>
-<issues format="4" by="lint 2.4.0-alpha6">
+<issues format="4" by="lint 3.0.0-alpha5">
 
 </issues>
diff --git a/compat/tests/java/android/support/v4/app/NotificationCompatTest.java b/compat/tests/java/android/support/v4/app/NotificationCompatTest.java
index 83460dc..dd870dd 100644
--- a/compat/tests/java/android/support/v4/app/NotificationCompatTest.java
+++ b/compat/tests/java/android/support/v4/app/NotificationCompatTest.java
@@ -85,12 +85,12 @@
     public void testTimeout() throws Throwable {
         long timeout = 23552;
         Notification n = new NotificationCompat.Builder(mActivityTestRule.getActivity())
-                .setTimeout(timeout)
+                .setTimeoutAfter(timeout)
                 .build();
         if (Build.VERSION.SDK_INT >= 26) {
-            assertEquals(timeout, NotificationCompat.getTimeout(n));
+            assertEquals(timeout, NotificationCompat.getTimeoutAfter(n));
         } else {
-            assertEquals(0, NotificationCompat.getTimeout(n));
+            assertEquals(0, NotificationCompat.getTimeoutAfter(n));
         }
     }
 
@@ -111,12 +111,12 @@
     public void testNotificationChannel() throws Throwable {
         String channelId = "new ID";
         Notification n  = new NotificationCompat.Builder(mActivityTestRule.getActivity())
-                .setChannel(channelId)
+                .setChannelId(channelId)
                 .build();
         if (Build.VERSION.SDK_INT >= 26) {
-            assertEquals(channelId, NotificationCompat.getChannel(n));
+            assertEquals(channelId, NotificationCompat.getChannelId(n));
         } else {
-            assertNull(NotificationCompat.getChannel(n));
+            assertNull(NotificationCompat.getChannelId(n));
         }
     }
 
@@ -126,9 +126,9 @@
         Notification n  = new NotificationCompat.Builder(mActivityTestRule.getActivity(), channelId)
                 .build();
         if (Build.VERSION.SDK_INT >= 26) {
-            assertEquals(channelId, NotificationCompat.getChannel(n));
+            assertEquals(channelId, NotificationCompat.getChannelId(n));
         } else {
-            assertNull(NotificationCompat.getChannel(n));
+            assertNull(NotificationCompat.getChannelId(n));
         }
     }
 
diff --git a/compat/tests/java/android/support/v4/graphics/PaintCompatHasGlyphTest.java b/compat/tests/java/android/support/v4/graphics/PaintCompatHasGlyphTest.java
index 26f0691..f17c881 100644
--- a/compat/tests/java/android/support/v4/graphics/PaintCompatHasGlyphTest.java
+++ b/compat/tests/java/android/support/v4/graphics/PaintCompatHasGlyphTest.java
@@ -47,6 +47,7 @@
                 {"\t\t\t", false},  // more white space
                 {"☺", SDK_INT >= 16}, // glyph added in API 16
                 {"\uD83D\uDC66\uD83C\uDFFF", SDK_INT >= 24}, // glyph added in API 24
+                {"\uD83C\uDDEF\uD83C\uDDF5", SDK_INT >= 20}, // Japan flag emoji, added in API 20
         });
     }
 
diff --git a/compat/tests/java/android/support/v4/util/ObjectsCompatTest.java b/compat/tests/java/android/support/v4/util/ObjectsCompatTest.java
new file mode 100644
index 0000000..9fdf451
--- /dev/null
+++ b/compat/tests/java/android/support/v4/util/ObjectsCompatTest.java
@@ -0,0 +1,51 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.support.v4.util;
+
+import static junit.framework.Assert.assertFalse;
+import static junit.framework.Assert.assertTrue;
+
+import android.support.test.filters.SmallTest;
+import android.support.test.runner.AndroidJUnit4;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+/**
+ * Unit tests for ObjectsCompat
+ */
+@RunWith(AndroidJUnit4.class)
+@SmallTest
+public class ObjectsCompatTest {
+
+    @Test
+    public void testEquals() throws Exception {
+        String a = "aaa";
+        String b = "bbb";
+        String c = new String(a);
+        String n = null;
+
+        assertFalse(ObjectsCompat.equals(a, b));
+        assertFalse(ObjectsCompat.equals(a, n));
+        assertFalse(ObjectsCompat.equals(n, a));
+
+        assertTrue(ObjectsCompat.equals(n, n));
+        assertTrue(ObjectsCompat.equals(a, a));
+        assertTrue(ObjectsCompat.equals(a, c));
+    }
+
+}
diff --git a/core-ui/java/android/support/v4/widget/DrawerLayout.java b/core-ui/java/android/support/v4/widget/DrawerLayout.java
index 3b3a781..c7b40e9 100644
--- a/core-ui/java/android/support/v4/widget/DrawerLayout.java
+++ b/core-ui/java/android/support/v4/widget/DrawerLayout.java
@@ -148,7 +148,8 @@
      */
     public static final int LOCK_MODE_UNDEFINED = 3;
 
-    @IntDef({Gravity.LEFT, Gravity.RIGHT, GravityCompat.START, GravityCompat.END})
+    @IntDef(value = {Gravity.LEFT, Gravity.RIGHT, GravityCompat.START, GravityCompat.END},
+            flag = true)
     @Retention(RetentionPolicy.SOURCE)
     private @interface EdgeGravity {}
 
diff --git a/core-ui/java/android/support/v4/widget/SlidingPaneLayout.java b/core-ui/java/android/support/v4/widget/SlidingPaneLayout.java
index c949cbb..e3f2462 100644
--- a/core-ui/java/android/support/v4/widget/SlidingPaneLayout.java
+++ b/core-ui/java/android/support/v4/widget/SlidingPaneLayout.java
@@ -477,7 +477,7 @@
         }
 
         int layoutHeight = 0;
-        int maxLayoutHeight = -1;
+        int maxLayoutHeight = 0;
         switch (heightMode) {
             case MeasureSpec.EXACTLY:
                 layoutHeight = maxLayoutHeight = heightSize - getPaddingTop() - getPaddingBottom();
diff --git a/core-ui/lint-baseline.xml b/core-ui/lint-baseline.xml
index 1c675b0..b373b5e 100644
--- a/core-ui/lint-baseline.xml
+++ b/core-ui/lint-baseline.xml
@@ -1,103 +1,15 @@
 <?xml version="1.0" encoding="UTF-8"?>
-<issues format="4" by="lint 2.4.0-alpha6">
-
-    <issue
-        id="Range"
-        message="Value must be ≥ 0 (was -1)"
-        errorLine1="                childHeightSpec = MeasureSpec.makeMeasureSpec(maxLayoutHeight, MeasureSpec.AT_MOST);"
-        errorLine2="                                                              ~~~~~~~~~~~~~~~">
-        <location
-            file="java/android/support/v4/widget/SlidingPaneLayout.java"
-            line="536"
-            column="63"/>
-    </issue>
-
-    <issue
-        id="Range"
-        message="Value must be ≥ 0 (was -1)"
-        errorLine1="                childHeightSpec = MeasureSpec.makeMeasureSpec(maxLayoutHeight, MeasureSpec.EXACTLY);"
-        errorLine2="                                                              ~~~~~~~~~~~~~~~">
-        <location
-            file="java/android/support/v4/widget/SlidingPaneLayout.java"
-            line="538"
-            column="63"/>
-    </issue>
-
-    <issue
-        id="Range"
-        message="Value must be ≥ 0 (was -1)"
-        errorLine1="                                childHeightSpec = MeasureSpec.makeMeasureSpec(maxLayoutHeight,"
-        errorLine2="                                                                              ~~~~~~~~~~~~~~~">
-        <location
-            file="java/android/support/v4/widget/SlidingPaneLayout.java"
-            line="586"
-            column="79"/>
-    </issue>
-
-    <issue
-        id="Range"
-        message="Value must be ≥ 0 (was -1)"
-        errorLine1="                                childHeightSpec = MeasureSpec.makeMeasureSpec(maxLayoutHeight,"
-        errorLine2="                                                                              ~~~~~~~~~~~~~~~">
-        <location
-            file="java/android/support/v4/widget/SlidingPaneLayout.java"
-            line="589"
-            column="79"/>
-    </issue>
-
-    <issue
-        id="Range"
-        message="Value must be ≥ 0 (was -1)"
-        errorLine1="                            childHeightSpec = MeasureSpec.makeMeasureSpec(maxLayoutHeight,"
-        errorLine2="                                                                          ~~~~~~~~~~~~~~~">
-        <location
-            file="java/android/support/v4/widget/SlidingPaneLayout.java"
-            line="608"
-            column="75"/>
-    </issue>
-
-    <issue
-        id="Range"
-        message="Value must be ≥ 0 (was -1)"
-        errorLine1="                            childHeightSpec = MeasureSpec.makeMeasureSpec(maxLayoutHeight,"
-        errorLine2="                                                                          ~~~~~~~~~~~~~~~">
-        <location
-            file="java/android/support/v4/widget/SlidingPaneLayout.java"
-            line="611"
-            column="75"/>
-    </issue>
+<issues format="4" by="lint 3.0.0-alpha5">
 
     <issue
         id="WrongConstant"
-        message="Must be one of: Gravity.LEFT, Gravity.RIGHT, GravityCompat.START, GravityCompat.END"
-        errorLine1="        setDrawerLockMode(lockMode, gravity);"
-        errorLine2="                                    ~~~~~~~">
+        message="Must be one or more of: Gravity.LEFT, Gravity.RIGHT, GravityCompat.START, GravityCompat.END"
+        errorLine1="                            + gravityToString(childGravity) + &quot; but this &quot; + TAG + &quot; already has a &quot;"
+        errorLine2="                                              ~~~~~~~~~~~~">
         <location
             file="java/android/support/v4/widget/DrawerLayout.java"
-            line="680"
-            column="37"/>
-    </issue>
-
-    <issue
-        id="WrongConstant"
-        message="Must be one of: Gravity.LEFT, Gravity.RIGHT, GravityCompat.START, GravityCompat.END"
-        errorLine1="        return getDrawerLockMode(drawerGravity);"
-        errorLine2="                                 ~~~~~~~~~~~~~">
-        <location
-            file="java/android/support/v4/widget/DrawerLayout.java"
-            line="753"
-            column="34"/>
-    </issue>
-
-    <issue
-        id="WrongConstant"
-        message="Flag not allowed here"
-        errorLine1="                        getDrawerViewAbsoluteGravity(child) &amp; Gravity.HORIZONTAL_GRAVITY_MASK;"
-        errorLine2="                        ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
-        <location
-            file="java/android/support/v4/widget/DrawerLayout.java"
-            line="1086"
-            column="25"/>
+            line="1075"
+            column="47"/>
     </issue>
 
 </issues>
diff --git a/core-utils/java/android/support/v4/content/WakefulBroadcastReceiver.java b/core-utils/java/android/support/v4/content/WakefulBroadcastReceiver.java
index b960939..b0cd653 100644
--- a/core-utils/java/android/support/v4/content/WakefulBroadcastReceiver.java
+++ b/core-utils/java/android/support/v4/content/WakefulBroadcastReceiver.java
@@ -70,8 +70,7 @@
 public abstract class WakefulBroadcastReceiver extends BroadcastReceiver {
     private static final String EXTRA_WAKE_LOCK_ID = "android.support.content.wakelockid";
 
-    private static final SparseArray<PowerManager.WakeLock> mActiveWakeLocks
-            = new SparseArray<PowerManager.WakeLock>();
+    private static final SparseArray<PowerManager.WakeLock> sActiveWakeLocks = new SparseArray<>();
     private static int mNextId = 1;
 
     /**
@@ -89,7 +88,7 @@
      * Context.startService}.
      */
     public static ComponentName startWakefulService(Context context, Intent intent) {
-        synchronized (mActiveWakeLocks) {
+        synchronized (sActiveWakeLocks) {
             int id = mNextId;
             mNextId++;
             if (mNextId <= 0) {
@@ -106,8 +105,8 @@
             PowerManager.WakeLock wl = pm.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK,
                     "wake:" + comp.flattenToShortString());
             wl.setReferenceCounted(false);
-            wl.acquire(60*1000);
-            mActiveWakeLocks.put(id, wl);
+            wl.acquire(60 * 1000);
+            sActiveWakeLocks.put(id, wl);
             return comp;
         }
     }
@@ -125,11 +124,11 @@
         if (id == 0) {
             return false;
         }
-        synchronized (mActiveWakeLocks) {
-            PowerManager.WakeLock wl = mActiveWakeLocks.get(id);
+        synchronized (sActiveWakeLocks) {
+            PowerManager.WakeLock wl = sActiveWakeLocks.get(id);
             if (wl != null) {
                 wl.release();
-                mActiveWakeLocks.remove(id);
+                sActiveWakeLocks.remove(id);
                 return true;
             }
             // We return true whether or not we actually found the wake lock
@@ -138,7 +137,7 @@
             // We just log a warning here if there is no wake lock found, which could
             // happen for example if this function is called twice on the same
             // intent or the process is killed and restarted before processing the intent.
-            Log.w("WakefulBroadcastReceiver", "No active wake lock id #" + id);
+            Log.w("WakefulBroadcastReceiv.", "No active wake lock id #" + id);
             return true;
         }
     }
diff --git a/core-utils/java/android/support/v4/graphics/drawable/RoundedBitmapDrawableFactory.java b/core-utils/java/android/support/v4/graphics/drawable/RoundedBitmapDrawableFactory.java
index 5efc74b..5e144c7 100644
--- a/core-utils/java/android/support/v4/graphics/drawable/RoundedBitmapDrawableFactory.java
+++ b/core-utils/java/android/support/v4/graphics/drawable/RoundedBitmapDrawableFactory.java
@@ -31,7 +31,7 @@
  * either from Bitmaps directly, or from streams and files.
  */
 public final class RoundedBitmapDrawableFactory {
-    private static final String TAG = "RoundedBitmapDrawableFactory";
+    private static final String TAG = "RoundedBitmapDrawableFa";
 
     private static class DefaultRoundedBitmapDrawable extends RoundedBitmapDrawable {
         DefaultRoundedBitmapDrawable(Resources res, Bitmap bitmap) {
diff --git a/core-utils/java/android/support/v4/utils/ObjectUtils.java b/core-utils/java/android/support/v4/utils/ObjectUtils.java
deleted file mode 100644
index cd4afc7..0000000
--- a/core-utils/java/android/support/v4/utils/ObjectUtils.java
+++ /dev/null
@@ -1,35 +0,0 @@
-/*
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package android.support.v4.utils;
-
-/**
- * This class consists of static utility methods for operating on objects.
- */
-public class ObjectUtils {
-    /**
-     * Returns true if the arguments are equal to each other and false otherwise.
-     * @param a an object
-     * @param b an object to be compared with <code>a</code> for equality
-     * @return <code>true</code> if the arguments are equal to each other and
-     *         <code>false</code> otherwise.
-     */
-    public static boolean objectEquals(Object a, Object b) {
-        return (a == b) || (a != null && a.equals(b));
-    }
-
-    private ObjectUtils() {
-    }
-}
diff --git a/core-utils/lint-baseline.xml b/core-utils/lint-baseline.xml
index d75d39e..fb44511 100644
--- a/core-utils/lint-baseline.xml
+++ b/core-utils/lint-baseline.xml
@@ -1,37 +1,4 @@
 <?xml version="1.0" encoding="UTF-8"?>
-<issues format="4" by="lint 2.4.0-alpha6">
-
-    <issue
-        id="LongLogTag"
-        message="The logging tag can be at most 23 characters, was 28 (RoundedBitmapDrawableFactory)"
-        errorLine1="            Log.w(TAG, &quot;RoundedBitmapDrawable cannot decode &quot; + filepath);"
-        errorLine2="                  ~~~">
-        <location
-            file="java/android/support/v4/graphics/drawable/RoundedBitmapDrawableFactory.java"
-            line="80"
-            column="19"/>
-    </issue>
-
-    <issue
-        id="LongLogTag"
-        message="The logging tag can be at most 23 characters, was 28 (RoundedBitmapDrawableFactory)"
-        errorLine1="            Log.w(TAG, &quot;RoundedBitmapDrawable cannot decode &quot; + is);"
-        errorLine2="                  ~~~">
-        <location
-            file="java/android/support/v4/graphics/drawable/RoundedBitmapDrawableFactory.java"
-            line="93"
-            column="19"/>
-    </issue>
-
-    <issue
-        id="LongLogTag"
-        message="The logging tag can be at most 23 characters, was 24 (WakefulBroadcastReceiver)"
-        errorLine1="            Log.w(&quot;WakefulBroadcastReceiver&quot;, &quot;No active wake lock id #&quot; + id);"
-        errorLine2="                  ~~~~~~~~~~~~~~~~~~~~~~~~~~">
-        <location
-            file="java/android/support/v4/content/WakefulBroadcastReceiver.java"
-            line="141"
-            column="19"/>
-    </issue>
+<issues format="4" by="lint 3.0.0-alpha5">
 
 </issues>
diff --git a/customtabs/lint-baseline.xml b/customtabs/lint-baseline.xml
index 172bbf6..fb44511 100644
--- a/customtabs/lint-baseline.xml
+++ b/customtabs/lint-baseline.xml
@@ -1,4 +1,4 @@
 <?xml version="1.0" encoding="UTF-8"?>
-<issues format="4" by="lint 2.4.0-alpha6">
+<issues format="4" by="lint 3.0.0-alpha5">
 
 </issues>
diff --git a/design/lint-baseline.xml b/design/lint-baseline.xml
index 7ece01b..d533bd0 100644
--- a/design/lint-baseline.xml
+++ b/design/lint-baseline.xml
@@ -1,5 +1,5 @@
 <?xml version="1.0" encoding="UTF-8"?>
-<issues format="4" by="lint 2.4.0-alpha6">
+<issues format="4" by="lint 3.0.0-alpha5">
 
     <issue
         id="MissingPermission"
@@ -14,28 +14,6 @@
 
     <issue
         id="MissingSuperCall"
-        message="Overriding method should call `super.onAnimationEnd`"
-        errorLine1="                public void onAnimationEnd(Animation animation) {"
-        errorLine2="                            ~~~~~~~~~~~~~~">
-        <location
-            file="java/android/support/v4/app/FragmentManager.java"
-            line="1604"
-            column="29"/>
-    </issue>
-
-    <issue
-        id="MissingSuperCall"
-        message="Overriding method should call `super.onAnimationEnd`"
-        errorLine1="        public void onAnimationEnd(Animation animation) {"
-        errorLine2="                    ~~~~~~~~~~~~~~">
-        <location
-            file="java/android/support/v4/app/FragmentManager.java"
-            line="3935"
-            column="21"/>
-    </issue>
-
-    <issue
-        id="MissingSuperCall"
         message="Overriding method should call `super.draw`"
         errorLine1="    public void draw(Canvas canvas) {"
         errorLine2="                ~~~~">
@@ -94,17 +72,6 @@
     </issue>
 
     <issue
-        id="ResourceType"
-        message="Expected resource of type xml"
-        errorLine1="            final XmlPullParser parser = res.getXml(resId);"
-        errorLine2="                                                    ~~~~~">
-        <location
-            file="src/android/support/graphics/drawable/VectorDrawableCompat.java"
-            line="557"
-            column="53"/>
-    </issue>
-
-    <issue
         id="Range"
         message="Value must be ≥ 0 (was -2147483648)"
         errorLine1="                                MeasureSpec.makeMeasureSpec(largestChildHeight,"
@@ -127,72 +94,6 @@
     </issue>
 
     <issue
-        id="Range"
-        message="Value must be ≥ 0 (was -1)"
-        errorLine1="                childHeightSpec = MeasureSpec.makeMeasureSpec(maxLayoutHeight, MeasureSpec.AT_MOST);"
-        errorLine2="                                                              ~~~~~~~~~~~~~~~">
-        <location
-            file="java/android/support/v4/widget/SlidingPaneLayout.java"
-            line="536"
-            column="63"/>
-    </issue>
-
-    <issue
-        id="Range"
-        message="Value must be ≥ 0 (was -1)"
-        errorLine1="                childHeightSpec = MeasureSpec.makeMeasureSpec(maxLayoutHeight, MeasureSpec.EXACTLY);"
-        errorLine2="                                                              ~~~~~~~~~~~~~~~">
-        <location
-            file="java/android/support/v4/widget/SlidingPaneLayout.java"
-            line="538"
-            column="63"/>
-    </issue>
-
-    <issue
-        id="Range"
-        message="Value must be ≥ 0 (was -1)"
-        errorLine1="                                childHeightSpec = MeasureSpec.makeMeasureSpec(maxLayoutHeight,"
-        errorLine2="                                                                              ~~~~~~~~~~~~~~~">
-        <location
-            file="java/android/support/v4/widget/SlidingPaneLayout.java"
-            line="586"
-            column="79"/>
-    </issue>
-
-    <issue
-        id="Range"
-        message="Value must be ≥ 0 (was -1)"
-        errorLine1="                                childHeightSpec = MeasureSpec.makeMeasureSpec(maxLayoutHeight,"
-        errorLine2="                                                                              ~~~~~~~~~~~~~~~">
-        <location
-            file="java/android/support/v4/widget/SlidingPaneLayout.java"
-            line="589"
-            column="79"/>
-    </issue>
-
-    <issue
-        id="Range"
-        message="Value must be ≥ 0 (was -1)"
-        errorLine1="                            childHeightSpec = MeasureSpec.makeMeasureSpec(maxLayoutHeight,"
-        errorLine2="                                                                          ~~~~~~~~~~~~~~~">
-        <location
-            file="java/android/support/v4/widget/SlidingPaneLayout.java"
-            line="608"
-            column="75"/>
-    </issue>
-
-    <issue
-        id="Range"
-        message="Value must be ≥ 0 (was -1)"
-        errorLine1="                            childHeightSpec = MeasureSpec.makeMeasureSpec(maxLayoutHeight,"
-        errorLine2="                                                                          ~~~~~~~~~~~~~~~">
-        <location
-            file="java/android/support/v4/widget/SlidingPaneLayout.java"
-            line="611"
-            column="75"/>
-    </issue>
-
-    <issue
         id="LongLogTag"
         message="The logging tag can be at most 23 characters, was 30 (ActionBarDrawerToggleHoneycomb)"
         errorLine1="                Log.w(TAG, &quot;Couldn&apos;t set home-as-up indicator via JB-MR2 API&quot;, e);"
@@ -292,362 +193,6 @@
     </issue>
 
     <issue
-        id="LongLogTag"
-        message="The logging tag can be at most 23 characters, was 25 (FragmentStatePagerAdapter)"
-        errorLine1="        if (DEBUG) Log.v(TAG, &quot;Adding item #&quot; + position + &quot;: f=&quot; + fragment);"
-        errorLine2="                         ~~~">
-        <location
-            file="java/android/support/v4/app/FragmentStatePagerAdapter.java"
-            line="110"
-            column="26"/>
-    </issue>
-
-    <issue
-        id="LongLogTag"
-        message="The logging tag can be at most 23 characters, was 25 (FragmentStatePagerAdapter)"
-        errorLine1="        if (DEBUG) Log.v(TAG, &quot;Removing item #&quot; + position + &quot;: f=&quot; + object"
-        errorLine2="                         ~~~">
-        <location
-            file="java/android/support/v4/app/FragmentStatePagerAdapter.java"
-            line="135"
-            column="26"/>
-    </issue>
-
-    <issue
-        id="LongLogTag"
-        message="The logging tag can be at most 23 characters, was 25 (FragmentStatePagerAdapter)"
-        errorLine1="                        Log.w(TAG, &quot;Bad fragment at key &quot; + key);"
-        errorLine2="                              ~~~">
-        <location
-            file="java/android/support/v4/app/FragmentStatePagerAdapter.java"
-            line="224"
-            column="31"/>
-    </issue>
-
-    <issue
-        id="LongLogTag"
-        message="The logging tag can be at most 23 characters, was 28 (RoundedBitmapDrawableFactory)"
-        errorLine1="            Log.w(TAG, &quot;RoundedBitmapDrawable cannot decode &quot; + filepath);"
-        errorLine2="                  ~~~">
-        <location
-            file="java/android/support/v4/graphics/drawable/RoundedBitmapDrawableFactory.java"
-            line="80"
-            column="19"/>
-    </issue>
-
-    <issue
-        id="LongLogTag"
-        message="The logging tag can be at most 23 characters, was 28 (RoundedBitmapDrawableFactory)"
-        errorLine1="            Log.w(TAG, &quot;RoundedBitmapDrawable cannot decode &quot; + is);"
-        errorLine2="                  ~~~">
-        <location
-            file="java/android/support/v4/graphics/drawable/RoundedBitmapDrawableFactory.java"
-            line="93"
-            column="19"/>
-    </issue>
-
-    <issue
-        id="LongLogTag"
-        message="The logging tag can be at most 23 characters, was 26 (StaggeredGridLayoutManager)"
-        errorLine1="            Log.d(TAG, &quot;found saved state: &quot; + mPendingSavedState);"
-        errorLine2="                  ~~~">
-        <location
-            file="src/android/support/v7/widget/StaggeredGridLayoutManager.java"
-            line="783"
-            column="19"/>
-    </issue>
-
-    <issue
-        id="LongLogTag"
-        message="The logging tag can be at most 23 characters, was 26 (StaggeredGridLayoutManager)"
-        errorLine1="            Log.d(TAG, &quot;Deciding anchor info from fresh state&quot;);"
-        errorLine2="                  ~~~">
-        <location
-            file="src/android/support/v7/widget/StaggeredGridLayoutManager.java"
-            line="828"
-            column="19"/>
-    </issue>
-
-    <issue
-        id="LongLogTag"
-        message="The logging tag can be at most 23 characters, was 26 (StaggeredGridLayoutManager)"
-        errorLine1="            Log.d(TAG, &quot;invalid saved state class&quot;);"
-        errorLine2="                  ~~~">
-        <location
-            file="src/android/support/v7/widget/StaggeredGridLayoutManager.java"
-            line="1187"
-            column="19"/>
-    </issue>
-
-    <issue
-        id="LongLogTag"
-        message="The logging tag can be at most 23 characters, was 26 (StaggeredGridLayoutManager)"
-        errorLine1="            Log.d(TAG, &quot;saved state:\n&quot; + state);"
-        errorLine2="                  ~~~">
-        <location
-            file="src/android/support/v7/widget/StaggeredGridLayoutManager.java"
-            line="1236"
-            column="19"/>
-    </issue>
-
-    <issue
-        id="LongLogTag"
-        message="The logging tag can be at most 23 characters, was 26 (StaggeredGridLayoutManager)"
-        errorLine1="            Log.d(TAG, &quot;FILLING targetLine: &quot; + targetLine + &quot;,&quot;"
-        errorLine2="                  ~~~">
-        <location
-            file="src/android/support/v7/widget/StaggeredGridLayoutManager.java"
-            line="1559"
-            column="19"/>
-    </issue>
-
-    <issue
-        id="LongLogTag"
-        message="The logging tag can be at most 23 characters, was 26 (StaggeredGridLayoutManager)"
-        errorLine1="                    Log.d(TAG, &quot;assigned &quot; + currentSpan.mIndex + &quot; for &quot; + position);"
-        errorLine2="                          ~~~">
-        <location
-            file="src/android/support/v7/widget/StaggeredGridLayoutManager.java"
-            line="1580"
-            column="27"/>
-    </issue>
-
-    <issue
-        id="LongLogTag"
-        message="The logging tag can be at most 23 characters, was 26 (StaggeredGridLayoutManager)"
-        errorLine1="                    Log.d(TAG, &quot;using &quot; + spanIndex + &quot; for pos &quot; + position);"
-        errorLine2="                          ~~~">
-        <location
-            file="src/android/support/v7/widget/StaggeredGridLayoutManager.java"
-            line="1584"
-            column="27"/>
-    </issue>
-
-    <issue
-        id="LongLogTag"
-        message="The logging tag can be at most 23 characters, was 26 (StaggeredGridLayoutManager)"
-        errorLine1="            Log.d(TAG, &quot;asked &quot; + dt + &quot; scrolled&quot; + totalScroll);"
-        errorLine2="                  ~~~">
-        <location
-            file="src/android/support/v7/widget/StaggeredGridLayoutManager.java"
-            line="2153"
-            column="19"/>
-    </issue>
-
-    <issue
-        id="LongLogTag"
-        message="The logging tag can be at most 23 characters, was 26 (StaggeredGridLayoutManager)"
-        errorLine1="                    Log.d(TAG, &quot;Unknown focus request:&quot; + focusDirection);"
-        errorLine2="                          ~~~">
-        <location
-            file="src/android/support/v7/widget/StaggeredGridLayoutManager.java"
-            line="2385"
-            column="27"/>
-    </issue>
-
-    <issue
-        id="LongLogTag"
-        message="The logging tag can be at most 23 characters, was 24 (WakefulBroadcastReceiver)"
-        errorLine1="            Log.w(&quot;WakefulBroadcastReceiver&quot;, &quot;No active wake lock id #&quot; + id);"
-        errorLine2="                  ~~~~~~~~~~~~~~~~~~~~~~~~~~">
-        <location
-            file="java/android/support/v4/content/WakefulBroadcastReceiver.java"
-            line="141"
-            column="19"/>
-    </issue>
-
-    <issue
-        id="AppCompatCustomView"
-        message="This custom view should extend `android.support.v7.widget.AppCompatAutoCompleteTextView` instead"
-        errorLine1="public class AppCompatAutoCompleteTextView extends AutoCompleteTextView implements"
-        errorLine2="                                                   ~~~~~~~~~~~~~~~~~~~~">
-        <location
-            file="src/android/support/v7/widget/AppCompatAutoCompleteTextView.java"
-            line="49"
-            column="52"/>
-    </issue>
-
-    <issue
-        id="AppCompatCustomView"
-        message="This custom view should extend `android.support.v7.widget.AppCompatButton` instead"
-        errorLine1="public class AppCompatButton extends Button implements TintableBackgroundView {"
-        errorLine2="                                     ~~~~~~">
-        <location
-            file="src/android/support/v7/widget/AppCompatButton.java"
-            line="51"
-            column="38"/>
-    </issue>
-
-    <issue
-        id="AppCompatCustomView"
-        message="This custom view should extend `android.support.v7.widget.AppCompatCheckBox` instead"
-        errorLine1="public class AppCompatCheckBox extends CheckBox implements TintableCompoundButton {"
-        errorLine2="                                       ~~~~~~~~">
-        <location
-            file="src/android/support/v7/widget/AppCompatCheckBox.java"
-            line="49"
-            column="40"/>
-    </issue>
-
-    <issue
-        id="AppCompatCustomView"
-        message="This custom view should extend `android.support.v7.widget.AppCompatCheckedTextView` instead"
-        errorLine1="public class AppCompatCheckedTextView extends CheckedTextView {"
-        errorLine2="                                              ~~~~~~~~~~~~~~~">
-        <location
-            file="src/android/support/v7/widget/AppCompatCheckedTextView.java"
-            line="33"
-            column="47"/>
-    </issue>
-
-    <issue
-        id="AppCompatCustomView"
-        message="This custom view should extend `android.support.v7.widget.AppCompatEditText` instead"
-        errorLine1="public class AppCompatEditText extends EditText implements TintableBackgroundView {"
-        errorLine2="                                       ~~~~~~~~">
-        <location
-            file="src/android/support/v7/widget/AppCompatEditText.java"
-            line="48"
-            column="40"/>
-    </issue>
-
-    <issue
-        id="AppCompatCustomView"
-        message="This custom view should extend `android.support.v7.widget.AppCompatImageButton` instead"
-        errorLine1="public class AppCompatImageButton extends ImageButton implements TintableBackgroundView,"
-        errorLine2="                                          ~~~~~~~~~~~">
-        <location
-            file="src/android/support/v7/widget/AppCompatImageButton.java"
-            line="58"
-            column="43"/>
-    </issue>
-
-    <issue
-        id="AppCompatCustomView"
-        message="This custom view should extend `android.support.v7.widget.AppCompatImageView` instead"
-        errorLine1="public class AppCompatImageView extends ImageView implements TintableBackgroundView,"
-        errorLine2="                                        ~~~~~~~~~">
-        <location
-            file="src/android/support/v7/widget/AppCompatImageView.java"
-            line="57"
-            column="41"/>
-    </issue>
-
-    <issue
-        id="AppCompatCustomView"
-        message="This custom view should extend `android.support.v7.widget.AppCompatMultiAutoCompleteTextView` instead"
-        errorLine1="public class AppCompatMultiAutoCompleteTextView extends MultiAutoCompleteTextView"
-        errorLine2="                                                        ~~~~~~~~~~~~~~~~~~~~~~~~~">
-        <location
-            file="src/android/support/v7/widget/AppCompatMultiAutoCompleteTextView.java"
-            line="49"
-            column="57"/>
-    </issue>
-
-    <issue
-        id="AppCompatCustomView"
-        message="This custom view should extend `android.support.v7.widget.AppCompatRadioButton` instead"
-        errorLine1="public class AppCompatRadioButton extends RadioButton implements TintableCompoundButton {"
-        errorLine2="                                          ~~~~~~~~~~~">
-        <location
-            file="src/android/support/v7/widget/AppCompatRadioButton.java"
-            line="49"
-            column="43"/>
-    </issue>
-
-    <issue
-        id="AppCompatCustomView"
-        message="This custom view should extend `android.support.v7.widget.AppCompatRatingBar` instead"
-        errorLine1="public class AppCompatRatingBar extends RatingBar {"
-        errorLine2="                                        ~~~~~~~~~">
-        <location
-            file="src/android/support/v7/widget/AppCompatRatingBar.java"
-            line="34"
-            column="41"/>
-    </issue>
-
-    <issue
-        id="AppCompatCustomView"
-        message="This custom view should extend `android.support.v7.widget.AppCompatSeekBar` instead"
-        errorLine1="public class AppCompatSeekBar extends SeekBar {"
-        errorLine2="                                      ~~~~~~~">
-        <location
-            file="src/android/support/v7/widget/AppCompatSeekBar.java"
-            line="34"
-            column="39"/>
-    </issue>
-
-    <issue
-        id="AppCompatCustomView"
-        message="This custom view should extend `android.support.v7.widget.AppCompatSpinner` instead"
-        errorLine1="public class AppCompatSpinner extends Spinner implements TintableBackgroundView {"
-        errorLine2="                                      ~~~~~~~">
-        <location
-            file="src/android/support/v7/widget/AppCompatSpinner.java"
-            line="68"
-            column="39"/>
-    </issue>
-
-    <issue
-        id="AppCompatCustomView"
-        message="This custom view should extend `android.support.v7.widget.AppCompatTextView` instead"
-        errorLine1="public class AppCompatTextView extends TextView implements TintableBackgroundView,"
-        errorLine2="                                       ~~~~~~~~">
-        <location
-            file="src/android/support/v7/widget/AppCompatTextView.java"
-            line="60"
-            column="40"/>
-    </issue>
-
-    <issue
-        id="AppCompatCustomView"
-        message="This custom view should extend `android.support.v7.widget.AppCompatImageView` instead"
-        errorLine1="class CircleImageView extends ImageView {"
-        errorLine2="                              ~~~~~~~~~">
-        <location
-            file="java/android/support/v4/widget/CircleImageView.java"
-            line="38"
-            column="31"/>
-    </issue>
-
-    <issue
-        id="AppCompatCustomView"
-        message="This custom view should extend `android.support.v7.widget.AppCompatTextView` instead"
-        errorLine1="public class DialogTitle extends TextView {"
-        errorLine2="                                 ~~~~~~~~">
-        <location
-            file="src/android/support/v7/widget/DialogTitle.java"
-            line="37"
-            column="34"/>
-    </issue>
-
-    <issue
-        id="AppCompatCustomView"
-        message="This custom view should extend `android.support.v7.widget.AppCompatImageButton` instead"
-        errorLine1="class VisibilityAwareImageButton extends ImageButton {"
-        errorLine2="                                         ~~~~~~~~~~~">
-        <location
-            file="base/android/support/design/widget/VisibilityAwareImageButton.java"
-            line="23"
-            column="42"/>
-    </issue>
-
-    <issue
-        id="UniqueConstants"
-        message="Constants `FLAG_CVE_EQ_PVE` and `FLAG_CVE_EQ_PVE` specify the same exact value (8192); this is usually a cut &amp; paste or merge error"
-        errorLine1="            FLAG_CVE_EQ_PVE, FLAG_CVE_EQ_PVE, FLAG_CVE_LT_PVE"
-        errorLine2="                             ~~~~~~~~~~~~~~~">
-        <location
-            file="src/android/support/v7/widget/ViewBoundsCheck.java"
-            line="125"
-            column="30"/>
-        <location
-            file="src/android/support/v7/widget/ViewBoundsCheck.java"
-            line="125"
-            column="13"/>
-    </issue>
-
-    <issue
         id="WrongConstant"
         message="Must be one of: PixelFormat.UNKNOWN, PixelFormat.TRANSLUCENT, PixelFormat.TRANSPARENT, PixelFormat.OPAQUE"
         errorLine1="        return 0;"
@@ -660,35 +205,13 @@
 
     <issue
         id="WrongConstant"
-        message="Must be one of: Gravity.LEFT, Gravity.RIGHT, GravityCompat.START, GravityCompat.END"
-        errorLine1="        setDrawerLockMode(lockMode, gravity);"
-        errorLine2="                                    ~~~~~~~">
+        message="Must be one or more of: Gravity.LEFT, Gravity.RIGHT, GravityCompat.START, GravityCompat.END"
+        errorLine1="                            + gravityToString(childGravity) + &quot; but this &quot; + TAG + &quot; already has a &quot;"
+        errorLine2="                                              ~~~~~~~~~~~~">
         <location
             file="java/android/support/v4/widget/DrawerLayout.java"
-            line="680"
-            column="37"/>
-    </issue>
-
-    <issue
-        id="WrongConstant"
-        message="Must be one of: Gravity.LEFT, Gravity.RIGHT, GravityCompat.START, GravityCompat.END"
-        errorLine1="        return getDrawerLockMode(drawerGravity);"
-        errorLine2="                                 ~~~~~~~~~~~~~">
-        <location
-            file="java/android/support/v4/widget/DrawerLayout.java"
-            line="753"
-            column="34"/>
-    </issue>
-
-    <issue
-        id="WrongConstant"
-        message="Flag not allowed here"
-        errorLine1="                        getDrawerViewAbsoluteGravity(child) &amp; Gravity.HORIZONTAL_GRAVITY_MASK;"
-        errorLine2="                        ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
-        <location
-            file="java/android/support/v4/widget/DrawerLayout.java"
-            line="1086"
-            column="25"/>
+            line="1075"
+            column="47"/>
     </issue>
 
     <issue
@@ -713,15 +236,4 @@
             column="36"/>
     </issue>
 
-    <issue
-        id="WrongConstant"
-        message="Must be one of: View.LAYOUT_DIRECTION_LTR, View.LAYOUT_DIRECTION_RTL"
-        errorLine1="            return isAutoMirrored() &amp;&amp; getLayoutDirection() == LayoutDirection.RTL;"
-        errorLine2="                                                               ~~~~~~~~~~~~~~~~~~~">
-        <location
-            file="src/android/support/graphics/drawable/VectorDrawableCompat.java"
-            line="821"
-            column="64"/>
-    </issue>
-
 </issues>
diff --git a/design/src/android/support/design/widget/AppBarLayout.java b/design/src/android/support/design/widget/AppBarLayout.java
index 8e465de..8304cd6 100644
--- a/design/src/android/support/design/widget/AppBarLayout.java
+++ b/design/src/android/support/design/widget/AppBarLayout.java
@@ -17,7 +17,6 @@
 package android.support.design.widget;
 
 import static android.support.annotation.RestrictTo.Scope.LIBRARY_GROUP;
-import static android.support.v4.utils.ObjectUtils.objectEquals;
 
 import android.animation.ValueAnimator;
 import android.content.Context;
@@ -34,6 +33,7 @@
 import android.support.annotation.VisibleForTesting;
 import android.support.design.R;
 import android.support.v4.math.MathUtils;
+import android.support.v4.util.ObjectsCompat;
 import android.support.v4.view.AbsSavedState;
 import android.support.v4.view.ViewCompat;
 import android.support.v4.view.WindowInsetsCompat;
@@ -602,7 +602,7 @@
         }
 
         // If our insets have changed, keep them and invalidate the scroll ranges...
-        if (!objectEquals(mLastInsets, newInsets)) {
+        if (!ObjectsCompat.equals(mLastInsets, newInsets)) {
             mLastInsets = newInsets;
             invalidateScrollRanges();
         }
diff --git a/design/src/android/support/design/widget/CollapsingToolbarLayout.java b/design/src/android/support/design/widget/CollapsingToolbarLayout.java
index 100b0dc..0051de9 100644
--- a/design/src/android/support/design/widget/CollapsingToolbarLayout.java
+++ b/design/src/android/support/design/widget/CollapsingToolbarLayout.java
@@ -17,7 +17,6 @@
 package android.support.design.widget;
 
 import static android.support.annotation.RestrictTo.Scope.LIBRARY_GROUP;
-import static android.support.v4.utils.ObjectUtils.objectEquals;
 
 import android.animation.ValueAnimator;
 import android.content.Context;
@@ -41,6 +40,7 @@
 import android.support.v4.content.ContextCompat;
 import android.support.v4.graphics.drawable.DrawableCompat;
 import android.support.v4.math.MathUtils;
+import android.support.v4.util.ObjectsCompat;
 import android.support.v4.view.GravityCompat;
 import android.support.v4.view.ViewCompat;
 import android.support.v4.view.WindowInsetsCompat;
@@ -270,7 +270,7 @@
         }
 
         // If our insets have changed, keep them and invalidate the scroll ranges...
-        if (!objectEquals(mLastInsets, newInsets)) {
+        if (!ObjectsCompat.equals(mLastInsets, newInsets)) {
             mLastInsets = newInsets;
             requestLayout();
         }
diff --git a/design/src/android/support/design/widget/CoordinatorLayout.java b/design/src/android/support/design/widget/CoordinatorLayout.java
index cc63050..818e3a6 100644
--- a/design/src/android/support/design/widget/CoordinatorLayout.java
+++ b/design/src/android/support/design/widget/CoordinatorLayout.java
@@ -17,7 +17,6 @@
 package android.support.design.widget;
 
 import static android.support.annotation.RestrictTo.Scope.LIBRARY_GROUP;
-import static android.support.v4.utils.ObjectUtils.objectEquals;
 
 import android.content.Context;
 import android.content.res.Resources;
@@ -46,6 +45,7 @@
 import android.support.v4.content.ContextCompat;
 import android.support.v4.graphics.drawable.DrawableCompat;
 import android.support.v4.math.MathUtils;
+import android.support.v4.util.ObjectsCompat;
 import android.support.v4.util.Pools;
 import android.support.v4.view.AbsSavedState;
 import android.support.v4.view.GravityCompat;
@@ -351,7 +351,7 @@
     }
 
     final WindowInsetsCompat setWindowInsets(WindowInsetsCompat insets) {
-        if (!objectEquals(mLastInsets, insets)) {
+        if (!ObjectsCompat.equals(mLastInsets, insets)) {
             mLastInsets = insets;
             mDrawStatusBarBackground = insets != null && insets.getSystemWindowInsetTop() > 0;
             setWillNotDraw(!mDrawStatusBarBackground && getBackground() == null);
diff --git a/dynamic-animation/lint-baseline.xml b/dynamic-animation/lint-baseline.xml
index d75d39e..fb44511 100644
--- a/dynamic-animation/lint-baseline.xml
+++ b/dynamic-animation/lint-baseline.xml
@@ -1,37 +1,4 @@
 <?xml version="1.0" encoding="UTF-8"?>
-<issues format="4" by="lint 2.4.0-alpha6">
-
-    <issue
-        id="LongLogTag"
-        message="The logging tag can be at most 23 characters, was 28 (RoundedBitmapDrawableFactory)"
-        errorLine1="            Log.w(TAG, &quot;RoundedBitmapDrawable cannot decode &quot; + filepath);"
-        errorLine2="                  ~~~">
-        <location
-            file="java/android/support/v4/graphics/drawable/RoundedBitmapDrawableFactory.java"
-            line="80"
-            column="19"/>
-    </issue>
-
-    <issue
-        id="LongLogTag"
-        message="The logging tag can be at most 23 characters, was 28 (RoundedBitmapDrawableFactory)"
-        errorLine1="            Log.w(TAG, &quot;RoundedBitmapDrawable cannot decode &quot; + is);"
-        errorLine2="                  ~~~">
-        <location
-            file="java/android/support/v4/graphics/drawable/RoundedBitmapDrawableFactory.java"
-            line="93"
-            column="19"/>
-    </issue>
-
-    <issue
-        id="LongLogTag"
-        message="The logging tag can be at most 23 characters, was 24 (WakefulBroadcastReceiver)"
-        errorLine1="            Log.w(&quot;WakefulBroadcastReceiver&quot;, &quot;No active wake lock id #&quot; + id);"
-        errorLine2="                  ~~~~~~~~~~~~~~~~~~~~~~~~~~">
-        <location
-            file="java/android/support/v4/content/WakefulBroadcastReceiver.java"
-            line="141"
-            column="19"/>
-    </issue>
+<issues format="4" by="lint 3.0.0-alpha5">
 
 </issues>
diff --git a/emoji/appcompat/lint-baseline.xml b/emoji/appcompat/lint-baseline.xml
index 91d58da..d533bd0 100644
--- a/emoji/appcompat/lint-baseline.xml
+++ b/emoji/appcompat/lint-baseline.xml
@@ -1,5 +1,5 @@
 <?xml version="1.0" encoding="UTF-8"?>
-<issues format="4" by="lint 2.4.0-alpha6">
+<issues format="4" by="lint 3.0.0-alpha5">
 
     <issue
         id="MissingPermission"
@@ -14,28 +14,6 @@
 
     <issue
         id="MissingSuperCall"
-        message="Overriding method should call `super.onAnimationEnd`"
-        errorLine1="                public void onAnimationEnd(Animation animation) {"
-        errorLine2="                            ~~~~~~~~~~~~~~">
-        <location
-            file="java/android/support/v4/app/FragmentManager.java"
-            line="1604"
-            column="29"/>
-    </issue>
-
-    <issue
-        id="MissingSuperCall"
-        message="Overriding method should call `super.onAnimationEnd`"
-        errorLine1="        public void onAnimationEnd(Animation animation) {"
-        errorLine2="                    ~~~~~~~~~~~~~~">
-        <location
-            file="java/android/support/v4/app/FragmentManager.java"
-            line="3935"
-            column="21"/>
-    </issue>
-
-    <issue
-        id="MissingSuperCall"
         message="Overriding method should call `super.draw`"
         errorLine1="    public void draw(Canvas canvas) {"
         errorLine2="                ~~~~">
@@ -94,17 +72,6 @@
     </issue>
 
     <issue
-        id="ResourceType"
-        message="Expected resource of type xml"
-        errorLine1="            final XmlPullParser parser = res.getXml(resId);"
-        errorLine2="                                                    ~~~~~">
-        <location
-            file="src/android/support/graphics/drawable/VectorDrawableCompat.java"
-            line="557"
-            column="53"/>
-    </issue>
-
-    <issue
         id="Range"
         message="Value must be ≥ 0 (was -2147483648)"
         errorLine1="                                MeasureSpec.makeMeasureSpec(largestChildHeight,"
@@ -127,72 +94,6 @@
     </issue>
 
     <issue
-        id="Range"
-        message="Value must be ≥ 0 (was -1)"
-        errorLine1="                childHeightSpec = MeasureSpec.makeMeasureSpec(maxLayoutHeight, MeasureSpec.AT_MOST);"
-        errorLine2="                                                              ~~~~~~~~~~~~~~~">
-        <location
-            file="java/android/support/v4/widget/SlidingPaneLayout.java"
-            line="536"
-            column="63"/>
-    </issue>
-
-    <issue
-        id="Range"
-        message="Value must be ≥ 0 (was -1)"
-        errorLine1="                childHeightSpec = MeasureSpec.makeMeasureSpec(maxLayoutHeight, MeasureSpec.EXACTLY);"
-        errorLine2="                                                              ~~~~~~~~~~~~~~~">
-        <location
-            file="java/android/support/v4/widget/SlidingPaneLayout.java"
-            line="538"
-            column="63"/>
-    </issue>
-
-    <issue
-        id="Range"
-        message="Value must be ≥ 0 (was -1)"
-        errorLine1="                                childHeightSpec = MeasureSpec.makeMeasureSpec(maxLayoutHeight,"
-        errorLine2="                                                                              ~~~~~~~~~~~~~~~">
-        <location
-            file="java/android/support/v4/widget/SlidingPaneLayout.java"
-            line="586"
-            column="79"/>
-    </issue>
-
-    <issue
-        id="Range"
-        message="Value must be ≥ 0 (was -1)"
-        errorLine1="                                childHeightSpec = MeasureSpec.makeMeasureSpec(maxLayoutHeight,"
-        errorLine2="                                                                              ~~~~~~~~~~~~~~~">
-        <location
-            file="java/android/support/v4/widget/SlidingPaneLayout.java"
-            line="589"
-            column="79"/>
-    </issue>
-
-    <issue
-        id="Range"
-        message="Value must be ≥ 0 (was -1)"
-        errorLine1="                            childHeightSpec = MeasureSpec.makeMeasureSpec(maxLayoutHeight,"
-        errorLine2="                                                                          ~~~~~~~~~~~~~~~">
-        <location
-            file="java/android/support/v4/widget/SlidingPaneLayout.java"
-            line="608"
-            column="75"/>
-    </issue>
-
-    <issue
-        id="Range"
-        message="Value must be ≥ 0 (was -1)"
-        errorLine1="                            childHeightSpec = MeasureSpec.makeMeasureSpec(maxLayoutHeight,"
-        errorLine2="                                                                          ~~~~~~~~~~~~~~~">
-        <location
-            file="java/android/support/v4/widget/SlidingPaneLayout.java"
-            line="611"
-            column="75"/>
-    </issue>
-
-    <issue
         id="LongLogTag"
         message="The logging tag can be at most 23 characters, was 30 (ActionBarDrawerToggleHoneycomb)"
         errorLine1="                Log.w(TAG, &quot;Couldn&apos;t set home-as-up indicator via JB-MR2 API&quot;, e);"
@@ -292,281 +193,6 @@
     </issue>
 
     <issue
-        id="LongLogTag"
-        message="The logging tag can be at most 23 characters, was 25 (FragmentStatePagerAdapter)"
-        errorLine1="        if (DEBUG) Log.v(TAG, &quot;Adding item #&quot; + position + &quot;: f=&quot; + fragment);"
-        errorLine2="                         ~~~">
-        <location
-            file="java/android/support/v4/app/FragmentStatePagerAdapter.java"
-            line="110"
-            column="26"/>
-    </issue>
-
-    <issue
-        id="LongLogTag"
-        message="The logging tag can be at most 23 characters, was 25 (FragmentStatePagerAdapter)"
-        errorLine1="        if (DEBUG) Log.v(TAG, &quot;Removing item #&quot; + position + &quot;: f=&quot; + object"
-        errorLine2="                         ~~~">
-        <location
-            file="java/android/support/v4/app/FragmentStatePagerAdapter.java"
-            line="135"
-            column="26"/>
-    </issue>
-
-    <issue
-        id="LongLogTag"
-        message="The logging tag can be at most 23 characters, was 25 (FragmentStatePagerAdapter)"
-        errorLine1="                        Log.w(TAG, &quot;Bad fragment at key &quot; + key);"
-        errorLine2="                              ~~~">
-        <location
-            file="java/android/support/v4/app/FragmentStatePagerAdapter.java"
-            line="224"
-            column="31"/>
-    </issue>
-
-    <issue
-        id="LongLogTag"
-        message="The logging tag can be at most 23 characters, was 28 (RoundedBitmapDrawableFactory)"
-        errorLine1="            Log.w(TAG, &quot;RoundedBitmapDrawable cannot decode &quot; + filepath);"
-        errorLine2="                  ~~~">
-        <location
-            file="java/android/support/v4/graphics/drawable/RoundedBitmapDrawableFactory.java"
-            line="80"
-            column="19"/>
-    </issue>
-
-    <issue
-        id="LongLogTag"
-        message="The logging tag can be at most 23 characters, was 28 (RoundedBitmapDrawableFactory)"
-        errorLine1="            Log.w(TAG, &quot;RoundedBitmapDrawable cannot decode &quot; + is);"
-        errorLine2="                  ~~~">
-        <location
-            file="java/android/support/v4/graphics/drawable/RoundedBitmapDrawableFactory.java"
-            line="93"
-            column="19"/>
-    </issue>
-
-    <issue
-        id="LongLogTag"
-        message="The logging tag can be at most 23 characters, was 24 (WakefulBroadcastReceiver)"
-        errorLine1="            Log.w(&quot;WakefulBroadcastReceiver&quot;, &quot;No active wake lock id #&quot; + id);"
-        errorLine2="                  ~~~~~~~~~~~~~~~~~~~~~~~~~~">
-        <location
-            file="java/android/support/v4/content/WakefulBroadcastReceiver.java"
-            line="141"
-            column="19"/>
-    </issue>
-
-    <issue
-        id="AppCompatCustomView"
-        message="This custom view should extend `android.support.v7.widget.AppCompatAutoCompleteTextView` instead"
-        errorLine1="public class AppCompatAutoCompleteTextView extends AutoCompleteTextView implements"
-        errorLine2="                                                   ~~~~~~~~~~~~~~~~~~~~">
-        <location
-            file="src/android/support/v7/widget/AppCompatAutoCompleteTextView.java"
-            line="49"
-            column="52"/>
-    </issue>
-
-    <issue
-        id="AppCompatCustomView"
-        message="This custom view should extend `android.support.v7.widget.AppCompatButton` instead"
-        errorLine1="public class AppCompatButton extends Button implements TintableBackgroundView {"
-        errorLine2="                                     ~~~~~~">
-        <location
-            file="src/android/support/v7/widget/AppCompatButton.java"
-            line="51"
-            column="38"/>
-    </issue>
-
-    <issue
-        id="AppCompatCustomView"
-        message="This custom view should extend `android.support.v7.widget.AppCompatCheckBox` instead"
-        errorLine1="public class AppCompatCheckBox extends CheckBox implements TintableCompoundButton {"
-        errorLine2="                                       ~~~~~~~~">
-        <location
-            file="src/android/support/v7/widget/AppCompatCheckBox.java"
-            line="49"
-            column="40"/>
-    </issue>
-
-    <issue
-        id="AppCompatCustomView"
-        message="This custom view should extend `android.support.v7.widget.AppCompatCheckedTextView` instead"
-        errorLine1="public class AppCompatCheckedTextView extends CheckedTextView {"
-        errorLine2="                                              ~~~~~~~~~~~~~~~">
-        <location
-            file="src/android/support/v7/widget/AppCompatCheckedTextView.java"
-            line="33"
-            column="47"/>
-    </issue>
-
-    <issue
-        id="AppCompatCustomView"
-        message="This custom view should extend `android.support.v7.widget.AppCompatEditText` instead"
-        errorLine1="public class AppCompatEditText extends EditText implements TintableBackgroundView {"
-        errorLine2="                                       ~~~~~~~~">
-        <location
-            file="src/android/support/v7/widget/AppCompatEditText.java"
-            line="48"
-            column="40"/>
-    </issue>
-
-    <issue
-        id="AppCompatCustomView"
-        message="This custom view should extend `android.support.v7.widget.AppCompatImageButton` instead"
-        errorLine1="public class AppCompatImageButton extends ImageButton implements TintableBackgroundView,"
-        errorLine2="                                          ~~~~~~~~~~~">
-        <location
-            file="src/android/support/v7/widget/AppCompatImageButton.java"
-            line="58"
-            column="43"/>
-    </issue>
-
-    <issue
-        id="AppCompatCustomView"
-        message="This custom view should extend `android.support.v7.widget.AppCompatImageView` instead"
-        errorLine1="public class AppCompatImageView extends ImageView implements TintableBackgroundView,"
-        errorLine2="                                        ~~~~~~~~~">
-        <location
-            file="src/android/support/v7/widget/AppCompatImageView.java"
-            line="57"
-            column="41"/>
-    </issue>
-
-    <issue
-        id="AppCompatCustomView"
-        message="This custom view should extend `android.support.v7.widget.AppCompatMultiAutoCompleteTextView` instead"
-        errorLine1="public class AppCompatMultiAutoCompleteTextView extends MultiAutoCompleteTextView"
-        errorLine2="                                                        ~~~~~~~~~~~~~~~~~~~~~~~~~">
-        <location
-            file="src/android/support/v7/widget/AppCompatMultiAutoCompleteTextView.java"
-            line="49"
-            column="57"/>
-    </issue>
-
-    <issue
-        id="AppCompatCustomView"
-        message="This custom view should extend `android.support.v7.widget.AppCompatRadioButton` instead"
-        errorLine1="public class AppCompatRadioButton extends RadioButton implements TintableCompoundButton {"
-        errorLine2="                                          ~~~~~~~~~~~">
-        <location
-            file="src/android/support/v7/widget/AppCompatRadioButton.java"
-            line="49"
-            column="43"/>
-    </issue>
-
-    <issue
-        id="AppCompatCustomView"
-        message="This custom view should extend `android.support.v7.widget.AppCompatRatingBar` instead"
-        errorLine1="public class AppCompatRatingBar extends RatingBar {"
-        errorLine2="                                        ~~~~~~~~~">
-        <location
-            file="src/android/support/v7/widget/AppCompatRatingBar.java"
-            line="34"
-            column="41"/>
-    </issue>
-
-    <issue
-        id="AppCompatCustomView"
-        message="This custom view should extend `android.support.v7.widget.AppCompatSeekBar` instead"
-        errorLine1="public class AppCompatSeekBar extends SeekBar {"
-        errorLine2="                                      ~~~~~~~">
-        <location
-            file="src/android/support/v7/widget/AppCompatSeekBar.java"
-            line="34"
-            column="39"/>
-    </issue>
-
-    <issue
-        id="AppCompatCustomView"
-        message="This custom view should extend `android.support.v7.widget.AppCompatSpinner` instead"
-        errorLine1="public class AppCompatSpinner extends Spinner implements TintableBackgroundView {"
-        errorLine2="                                      ~~~~~~~">
-        <location
-            file="src/android/support/v7/widget/AppCompatSpinner.java"
-            line="68"
-            column="39"/>
-    </issue>
-
-    <issue
-        id="AppCompatCustomView"
-        message="This custom view should extend `android.support.v7.widget.AppCompatTextView` instead"
-        errorLine1="public class AppCompatTextView extends TextView implements TintableBackgroundView,"
-        errorLine2="                                       ~~~~~~~~">
-        <location
-            file="src/android/support/v7/widget/AppCompatTextView.java"
-            line="60"
-            column="40"/>
-    </issue>
-
-    <issue
-        id="AppCompatCustomView"
-        message="This custom view should extend `android.support.v7.widget.AppCompatImageView` instead"
-        errorLine1="class CircleImageView extends ImageView {"
-        errorLine2="                              ~~~~~~~~~">
-        <location
-            file="java/android/support/v4/widget/CircleImageView.java"
-            line="38"
-            column="31"/>
-    </issue>
-
-    <issue
-        id="AppCompatCustomView"
-        message="This custom view should extend `android.support.v7.widget.AppCompatTextView` instead"
-        errorLine1="public class DialogTitle extends TextView {"
-        errorLine2="                                 ~~~~~~~~">
-        <location
-            file="src/android/support/v7/widget/DialogTitle.java"
-            line="37"
-            column="34"/>
-    </issue>
-
-    <issue
-        id="AppCompatCustomView"
-        message="This custom view should extend `android.support.v7.widget.AppCompatButton` instead"
-        errorLine1="public class EmojiButton extends Button {"
-        errorLine2="                                 ~~~~~~">
-        <location
-            file="src/android/support/text/emoji/widget/EmojiButton.java"
-            line="29"
-            column="34"/>
-    </issue>
-
-    <issue
-        id="AppCompatCustomView"
-        message="This custom view should extend `android.support.v7.widget.AppCompatEditText` instead"
-        errorLine1="public class EmojiEditText extends EditText {"
-        errorLine2="                                   ~~~~~~~~">
-        <location
-            file="src/android/support/text/emoji/widget/EmojiEditText.java"
-            line="33"
-            column="36"/>
-    </issue>
-
-    <issue
-        id="AppCompatCustomView"
-        message="This custom view should extend `android.support.v7.widget.AppCompatTextView` instead"
-        errorLine1="public class EmojiTextView extends TextView {"
-        errorLine2="                                   ~~~~~~~~">
-        <location
-            file="src/android/support/text/emoji/widget/EmojiTextView.java"
-            line="29"
-            column="36"/>
-    </issue>
-
-    <issue
-        id="AppCompatCustomView"
-        message="This custom view should extend `android.support.v7.widget.AppCompatButton` instead"
-        errorLine1="public class ExtractButtonCompat extends Button {"
-        errorLine2="                                         ~~~~~~">
-        <location
-            file="src/android/support/text/emoji/widget/ExtractButtonCompat.java"
-            line="34"
-            column="42"/>
-    </issue>
-
-    <issue
         id="WrongConstant"
         message="Must be one of: PixelFormat.UNKNOWN, PixelFormat.TRANSLUCENT, PixelFormat.TRANSPARENT, PixelFormat.OPAQUE"
         errorLine1="        return 0;"
@@ -579,35 +205,13 @@
 
     <issue
         id="WrongConstant"
-        message="Must be one of: Gravity.LEFT, Gravity.RIGHT, GravityCompat.START, GravityCompat.END"
-        errorLine1="        setDrawerLockMode(lockMode, gravity);"
-        errorLine2="                                    ~~~~~~~">
+        message="Must be one or more of: Gravity.LEFT, Gravity.RIGHT, GravityCompat.START, GravityCompat.END"
+        errorLine1="                            + gravityToString(childGravity) + &quot; but this &quot; + TAG + &quot; already has a &quot;"
+        errorLine2="                                              ~~~~~~~~~~~~">
         <location
             file="java/android/support/v4/widget/DrawerLayout.java"
-            line="680"
-            column="37"/>
-    </issue>
-
-    <issue
-        id="WrongConstant"
-        message="Must be one of: Gravity.LEFT, Gravity.RIGHT, GravityCompat.START, GravityCompat.END"
-        errorLine1="        return getDrawerLockMode(drawerGravity);"
-        errorLine2="                                 ~~~~~~~~~~~~~">
-        <location
-            file="java/android/support/v4/widget/DrawerLayout.java"
-            line="753"
-            column="34"/>
-    </issue>
-
-    <issue
-        id="WrongConstant"
-        message="Flag not allowed here"
-        errorLine1="                        getDrawerViewAbsoluteGravity(child) &amp; Gravity.HORIZONTAL_GRAVITY_MASK;"
-        errorLine2="                        ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
-        <location
-            file="java/android/support/v4/widget/DrawerLayout.java"
-            line="1086"
-            column="25"/>
+            line="1075"
+            column="47"/>
     </issue>
 
     <issue
@@ -632,15 +236,4 @@
             column="36"/>
     </issue>
 
-    <issue
-        id="WrongConstant"
-        message="Must be one of: View.LAYOUT_DIRECTION_LTR, View.LAYOUT_DIRECTION_RTL"
-        errorLine1="            return isAutoMirrored() &amp;&amp; getLayoutDirection() == LayoutDirection.RTL;"
-        errorLine2="                                                               ~~~~~~~~~~~~~~~~~~~">
-        <location
-            file="src/android/support/graphics/drawable/VectorDrawableCompat.java"
-            line="821"
-            column="64"/>
-    </issue>
-
 </issues>
diff --git a/emoji/appcompat/src/android/support/text/emoji/widget/EmojiAppCompatEditText.java b/emoji/appcompat/src/android/support/text/emoji/widget/EmojiAppCompatEditText.java
index d640f0f..87c17c2 100644
--- a/emoji/appcompat/src/android/support/text/emoji/widget/EmojiAppCompatEditText.java
+++ b/emoji/appcompat/src/android/support/text/emoji/widget/EmojiAppCompatEditText.java
@@ -29,6 +29,8 @@
  * AppCompatEditText widget enhanced with emoji capability by using {@link EmojiEditTextHelper}.
  * When used on devices running API 18 or below, this widget acts as a regular
  * {@link AppCompatEditText}.
+ *
+ * @attr ref android.support.text.emoji.R.styleable#EmojiEditText_maxEmojiCount
  */
 public class EmojiAppCompatEditText extends AppCompatEditText {
     private EmojiEditTextHelper mEmojiEditTextHelper;
@@ -58,7 +60,7 @@
         if (!mInitialized) {
             mInitialized = true;
             final EditTextAttributeHelper attrHelper = new EditTextAttributeHelper(this, attrs,
-                    defStyleAttr);
+                    defStyleAttr, 0);
             setMaxEmojiCount(attrHelper.getMaxEmojiCount());
             setKeyListener(super.getKeyListener());
         }
@@ -84,6 +86,8 @@
      *                      should be equal or greater than 0
      *
      * @see EmojiCompat#process(CharSequence, int, int, int)
+     *
+     * @attr ref android.support.text.emoji.R.styleable#EmojiEditText_maxEmojiCount
      */
     public void setMaxEmojiCount(@IntRange(from = 0) int maxEmojiCount) {
         getEmojiEditTextHelper().setMaxEmojiCount(maxEmojiCount);
@@ -94,6 +98,8 @@
      *
      * @see #setMaxEmojiCount(int)
      * @see EmojiCompat#process(CharSequence, int, int, int)
+     *
+     * @attr ref android.support.text.emoji.R.styleable#EmojiEditText_maxEmojiCount
      */
     public int getMaxEmojiCount() {
         return getEmojiEditTextHelper().getMaxEmojiCount();
diff --git a/emoji/bundled/lint-baseline.xml b/emoji/bundled/lint-baseline.xml
index 172bbf6..fb44511 100644
--- a/emoji/bundled/lint-baseline.xml
+++ b/emoji/bundled/lint-baseline.xml
@@ -1,4 +1,4 @@
 <?xml version="1.0" encoding="UTF-8"?>
-<issues format="4" by="lint 2.4.0-alpha6">
+<issues format="4" by="lint 3.0.0-alpha5">
 
 </issues>
diff --git a/emoji/core/lint-baseline.xml b/emoji/core/lint-baseline.xml
index 172bbf6..fb44511 100644
--- a/emoji/core/lint-baseline.xml
+++ b/emoji/core/lint-baseline.xml
@@ -1,4 +1,4 @@
 <?xml version="1.0" encoding="UTF-8"?>
-<issues format="4" by="lint 2.4.0-alpha6">
+<issues format="4" by="lint 3.0.0-alpha5">
 
 </issues>
diff --git a/emoji/core/res-public/values/public_attrs.xml b/emoji/core/res-public/values/public_attrs.xml
index 56f49ff..56634ff 100644
--- a/emoji/core/res-public/values/public_attrs.xml
+++ b/emoji/core/res-public/values/public_attrs.xml
@@ -18,4 +18,5 @@
 <!-- Definitions of attributes to be exposed as public -->
 <resources>
     <public type="attr" name="maxEmojiCount"/>
+    <public type="attr" name="emojiReplaceStrategy"/>
 </resources>
diff --git a/emoji/core/res/values/attrs.xml b/emoji/core/res/values/attrs.xml
index d34bb7c..77254a4 100644
--- a/emoji/core/res/values/attrs.xml
+++ b/emoji/core/res/values/attrs.xml
@@ -18,4 +18,16 @@
     <declare-styleable name="EmojiEditText">
         <attr name="maxEmojiCount" format="integer"/>
     </declare-styleable>
+    <declare-styleable name="EmojiExtractTextLayout">
+        <attr name="emojiReplaceStrategy" format="enum">
+            <!-- Replace strategy that uses the value given in EmojiCompat.Config. Default
+            value. -->
+            <enum name="defaultStrategy" value="0" />
+            <!-- Replace strategy to add EmojiSpans for all emoji that were found. -->
+            <enum name="all" value="1" />
+            <!-- Replace strategy to add EmojiSpans only for emoji that do not exist in the
+            system. -->
+            <enum name="nonExistent" value="2" />
+        </attr>
+    </declare-styleable>
 </resources>
diff --git a/emoji/core/src/android/support/text/emoji/EmojiCompat.java b/emoji/core/src/android/support/text/emoji/EmojiCompat.java
index eb1c565..f258c12 100644
--- a/emoji/core/src/android/support/text/emoji/EmojiCompat.java
+++ b/emoji/core/src/android/support/text/emoji/EmojiCompat.java
@@ -669,6 +669,19 @@
     }
 
     /**
+     * Returns signature for the currently loaded emoji assets. The signature is a SHA that is
+     * constructed using emoji assets. Can be used to detect if currently loaded asset is different
+     * then previous executions. When used on devices running API 18 or below, returns empty string.
+     *
+     * @throws IllegalStateException if not initialized yet
+     */
+    @NonNull
+    public String getAssetSignature() {
+        Preconditions.checkState(isInitialized(), "Not initialized yet");
+        return mHelper.getAssetSignature();
+    }
+
+    /**
      * Updates the EditorInfo attributes in order to communicate information to Keyboards. When
      * used on devices running API 18 or below, does not update EditorInfo attributes.
      *
@@ -951,6 +964,10 @@
         void setGlyphChecker(@NonNull EmojiProcessor.GlyphChecker glyphChecker) {
             // intentionally empty
         }
+
+        String getAssetSignature() {
+            return "";
+        }
     }
 
     @RequiresApi(19)
@@ -1032,5 +1049,11 @@
         void setGlyphChecker(@NonNull EmojiProcessor.GlyphChecker glyphChecker) {
             mProcessor.setGlyphChecker(glyphChecker);
         }
+
+        @Override
+        String getAssetSignature() {
+            final String sha = mMetadataRepo.getMetadataList().sourceSha();
+            return sha == null ? "" : sha;
+        }
     }
 }
diff --git a/emoji/core/src/android/support/text/emoji/widget/EditTextAttributeHelper.java b/emoji/core/src/android/support/text/emoji/widget/EditTextAttributeHelper.java
index cf3bfad..381a02c 100644
--- a/emoji/core/src/android/support/text/emoji/widget/EditTextAttributeHelper.java
+++ b/emoji/core/src/android/support/text/emoji/widget/EditTextAttributeHelper.java
@@ -36,11 +36,12 @@
     static final int MAX_EMOJI_COUNT = Integer.MAX_VALUE;
     private int mMaxEmojiCount;
 
-    public EditTextAttributeHelper(@NonNull View view, AttributeSet attrs, int defStyleAttr) {
+    public EditTextAttributeHelper(@NonNull View view, AttributeSet attrs, int defStyleAttr,
+            int defStyleRes) {
         if (attrs != null) {
             final Context context = view.getContext();
             TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.EmojiEditText,
-                    defStyleAttr, 0);
+                    defStyleAttr, defStyleRes);
             mMaxEmojiCount = a.getInteger(R.styleable.EmojiEditText_maxEmojiCount, MAX_EMOJI_COUNT);
             a.recycle();
         }
diff --git a/emoji/core/src/android/support/text/emoji/widget/EmojiEditText.java b/emoji/core/src/android/support/text/emoji/widget/EmojiEditText.java
index ce24f3a..a0e8a69 100644
--- a/emoji/core/src/android/support/text/emoji/widget/EmojiEditText.java
+++ b/emoji/core/src/android/support/text/emoji/widget/EmojiEditText.java
@@ -29,42 +29,44 @@
 /**
  * EditText widget enhanced with emoji capability by using {@link EmojiEditTextHelper}. When used
  * on devices running API 18 or below, this widget acts as a regular {@link EditText}.
+ *
+ * @attr ref android.support.text.emoji.R.styleable#EmojiEditText_maxEmojiCount
  */
 public class EmojiEditText extends EditText {
     private EmojiEditTextHelper mEmojiEditTextHelper;
 
     /**
-     * Prevent calling {@link #init(AttributeSet, int)} multiple times in case super() constructors
-     * call other constructors.
+     * Prevent calling {@link #init(AttributeSet, int, int)} multiple times in case super()
+     * constructors call other constructors.
      */
     private boolean mInitialized;
 
     public EmojiEditText(Context context) {
         super(context);
-        init(null /*attrs*/, 0 /*defStyleAttr*/);
+        init(null /*attrs*/, 0 /*defStyleAttr*/, 0 /*defStyleRes*/);
     }
 
     public EmojiEditText(Context context, AttributeSet attrs) {
         super(context, attrs);
-        init(attrs, android.R.attr.editTextStyle);
+        init(attrs, android.R.attr.editTextStyle, 0 /*defStyleRes*/);
     }
 
     public EmojiEditText(Context context, AttributeSet attrs, int defStyleAttr) {
         super(context, attrs, defStyleAttr);
-        init(attrs, defStyleAttr);
+        init(attrs, defStyleAttr, 0 /*defStyleRes*/);
     }
 
     @TargetApi(Build.VERSION_CODES.LOLLIPOP)
     public EmojiEditText(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
         super(context, attrs, defStyleAttr, defStyleRes);
-        init(attrs, defStyleAttr);
+        init(attrs, defStyleAttr, defStyleRes);
     }
 
-    private void init(@Nullable AttributeSet attrs, int defStyleAttr) {
+    private void init(@Nullable AttributeSet attrs, int defStyleAttr, int defStyleRes) {
         if (!mInitialized) {
             mInitialized = true;
             final EditTextAttributeHelper attrHelper = new EditTextAttributeHelper(this, attrs,
-                    defStyleAttr);
+                    defStyleAttr, defStyleRes);
             setMaxEmojiCount(attrHelper.getMaxEmojiCount());
             setKeyListener(super.getKeyListener());
         }
@@ -90,6 +92,8 @@
      *                      should be equal or greater than 0
      *
      * @see EmojiCompat#process(CharSequence, int, int, int)
+     *
+     * @attr ref android.support.text.emoji.R.styleable#EmojiEditText_maxEmojiCount
      */
     public void setMaxEmojiCount(@IntRange(from = 0) int maxEmojiCount) {
         getEmojiEditTextHelper().setMaxEmojiCount(maxEmojiCount);
@@ -100,6 +104,8 @@
      *
      * @see #setMaxEmojiCount(int)
      * @see EmojiCompat#process(CharSequence, int, int, int)
+     *
+     * @attr ref android.support.text.emoji.R.styleable#EmojiEditText_maxEmojiCount
      */
     public int getMaxEmojiCount() {
         return getEmojiEditTextHelper().getMaxEmojiCount();
diff --git a/emoji/core/src/android/support/text/emoji/widget/EmojiEditTextHelper.java b/emoji/core/src/android/support/text/emoji/widget/EmojiEditTextHelper.java
index b010cf7..edc511f 100644
--- a/emoji/core/src/android/support/text/emoji/widget/EmojiEditTextHelper.java
+++ b/emoji/core/src/android/support/text/emoji/widget/EmojiEditTextHelper.java
@@ -15,11 +15,15 @@
  */
 package android.support.text.emoji.widget;
 
+import static android.support.annotation.RestrictTo.Scope.LIBRARY_GROUP;
+
 import android.os.Build;
 import android.support.annotation.IntRange;
 import android.support.annotation.NonNull;
 import android.support.annotation.RequiresApi;
+import android.support.annotation.RestrictTo;
 import android.support.text.emoji.EmojiCompat;
+import android.support.text.emoji.EmojiSpan;
 import android.support.v4.util.Preconditions;
 import android.text.method.KeyListener;
 import android.view.inputmethod.EditorInfo;
@@ -63,9 +67,10 @@
  *
  */
 public final class EmojiEditTextHelper {
-
     private final HelperInternal mHelper;
-    private int mMaxEmojiCount;
+    private int mMaxEmojiCount = EditTextAttributeHelper.MAX_EMOJI_COUNT;
+    @EmojiCompat.ReplaceStrategy
+    private int mEmojiReplaceStrategy = EmojiCompat.REPLACE_STRATEGY_DEFAULT;
 
     /**
      * Default constructor.
@@ -96,7 +101,6 @@
         mHelper.setMaxEmojiCount(maxEmojiCount);
     }
 
-
     /**
      * Returns the maximum number of EmojiSpans to be added to a CharSequence.
      *
@@ -141,6 +145,36 @@
         return mHelper.onCreateInputConnection(inputConnection, outAttrs);
     }
 
+    /**
+     * Sets whether to replace all emoji with {@link EmojiSpan}s. Default value is
+     * {@link EmojiCompat#REPLACE_STRATEGY_DEFAULT}.
+     *
+     * @param replaceStrategy should be one of {@link EmojiCompat#REPLACE_STRATEGY_DEFAULT},
+     *                        {@link EmojiCompat#REPLACE_STRATEGY_NON_EXISTENT},
+     *                        {@link EmojiCompat#REPLACE_STRATEGY_ALL}
+     *
+     * @hide
+     */
+    @RestrictTo(LIBRARY_GROUP)
+    void setEmojiReplaceStrategy(@EmojiCompat.ReplaceStrategy int replaceStrategy) {
+        mEmojiReplaceStrategy = replaceStrategy;
+        mHelper.setEmojiReplaceStrategy(replaceStrategy);
+    }
+
+    /**
+     * Returns whether to replace all emoji with {@link EmojiSpan}s. Default value is
+     * {@link EmojiCompat#REPLACE_STRATEGY_DEFAULT}.
+     *
+     * @return one of {@link EmojiCompat#REPLACE_STRATEGY_DEFAULT},
+     *                        {@link EmojiCompat#REPLACE_STRATEGY_NON_EXISTENT},
+     *                        {@link EmojiCompat#REPLACE_STRATEGY_ALL}
+     * @hide
+     */
+    @RestrictTo(LIBRARY_GROUP)
+    int getEmojiReplaceStrategy() {
+        return mEmojiReplaceStrategy;
+    }
+
     private static class HelperInternal {
 
         KeyListener getKeyListener(@NonNull KeyListener keyListener) {
@@ -152,7 +186,11 @@
             return inputConnection;
         }
 
-        public void setMaxEmojiCount(int maxEmojiCount) {
+        void setMaxEmojiCount(int maxEmojiCount) {
+            // do nothing
+        }
+
+        void setEmojiReplaceStrategy(@EmojiCompat.ReplaceStrategy int replaceStrategy) {
             // do nothing
         }
     }
@@ -170,11 +208,16 @@
         }
 
         @Override
-        public void setMaxEmojiCount(int maxEmojiCount) {
+        void setMaxEmojiCount(int maxEmojiCount) {
             mTextWatcher.setMaxEmojiCount(maxEmojiCount);
         }
 
         @Override
+        void setEmojiReplaceStrategy(@EmojiCompat.ReplaceStrategy int replaceStrategy) {
+            mTextWatcher.setEmojiReplaceStrategy(replaceStrategy);
+        }
+
+        @Override
         KeyListener getKeyListener(@NonNull final KeyListener keyListener) {
             if (keyListener instanceof EmojiKeyListener) {
                 return keyListener;
diff --git a/emoji/core/src/android/support/text/emoji/widget/EmojiExtractEditText.java b/emoji/core/src/android/support/text/emoji/widget/EmojiExtractEditText.java
index c15914b..ca1868e 100644
--- a/emoji/core/src/android/support/text/emoji/widget/EmojiExtractEditText.java
+++ b/emoji/core/src/android/support/text/emoji/widget/EmojiExtractEditText.java
@@ -26,6 +26,7 @@
 import android.support.annotation.Nullable;
 import android.support.annotation.RestrictTo;
 import android.support.text.emoji.EmojiCompat;
+import android.support.text.emoji.EmojiSpan;
 import android.util.AttributeSet;
 import android.view.inputmethod.EditorInfo;
 import android.view.inputmethod.InputConnection;
@@ -49,31 +50,31 @@
 
     public EmojiExtractEditText(Context context) {
         super(context);
-        init(null /*attrs*/, 0 /*defStyleAttr*/);
+        init(null /*attrs*/, 0 /*defStyleAttr*/, 0 /*defStyleRes*/);
     }
 
     public EmojiExtractEditText(Context context, AttributeSet attrs) {
         super(context, attrs);
-        init(attrs, android.R.attr.editTextStyle);
+        init(attrs, android.R.attr.editTextStyle, 0 /*defStyleRes*/);
     }
 
     public EmojiExtractEditText(Context context, AttributeSet attrs, int defStyleAttr) {
         super(context, attrs, defStyleAttr);
-        init(attrs, defStyleAttr);
+        init(attrs, defStyleAttr, 0 /*defStyleRes*/);
     }
 
     @TargetApi(Build.VERSION_CODES.LOLLIPOP)
     public EmojiExtractEditText(Context context, AttributeSet attrs, int defStyleAttr,
             int defStyleRes) {
         super(context, attrs, defStyleAttr, defStyleRes);
-        init(attrs, defStyleAttr);
+        init(attrs, defStyleAttr, defStyleRes);
     }
 
-    private void init(@Nullable AttributeSet attrs, int defStyleAttr) {
+    private void init(@Nullable AttributeSet attrs, int defStyleAttr, int defStyleRes) {
         if (!mInitialized) {
             mInitialized = true;
             final EditTextAttributeHelper attrHelper = new EditTextAttributeHelper(this, attrs,
-                    defStyleAttr);
+                    defStyleAttr, defStyleRes);
             setMaxEmojiCount(attrHelper.getMaxEmojiCount());
             setKeyListener(super.getKeyListener());
         }
@@ -104,6 +105,30 @@
     }
 
     /**
+     * Sets whether to replace all emoji with {@link EmojiSpan}s. Default value is
+     * {@link EmojiCompat#REPLACE_STRATEGY_DEFAULT}.
+     *
+     * @param replaceStrategy should be one of {@link EmojiCompat#REPLACE_STRATEGY_DEFAULT},
+     *                        {@link EmojiCompat#REPLACE_STRATEGY_NON_EXISTENT},
+     *                        {@link EmojiCompat#REPLACE_STRATEGY_ALL}
+     */
+    public void setEmojiReplaceStrategy(@EmojiCompat.ReplaceStrategy int replaceStrategy) {
+        getEmojiEditTextHelper().setEmojiReplaceStrategy(replaceStrategy);
+    }
+
+    /**
+     * Returns whether to replace all emoji with {@link EmojiSpan}s. Default value is
+     * {@link EmojiCompat#REPLACE_STRATEGY_DEFAULT}.
+     *
+     * @return one of {@link EmojiCompat#REPLACE_STRATEGY_DEFAULT},
+     *                        {@link EmojiCompat#REPLACE_STRATEGY_NON_EXISTENT},
+     *                        {@link EmojiCompat#REPLACE_STRATEGY_ALL}
+     */
+    public int getEmojiReplaceStrategy() {
+        return getEmojiEditTextHelper().getEmojiReplaceStrategy();
+    }
+
+    /**
      * Returns the maximum number of EmojiSpans to be added to a CharSequence.
      *
      * @see #setMaxEmojiCount(int)
diff --git a/emoji/core/src/android/support/text/emoji/widget/EmojiExtractTextLayout.java b/emoji/core/src/android/support/text/emoji/widget/EmojiExtractTextLayout.java
index 12e9d9a..c75d6d0 100644
--- a/emoji/core/src/android/support/text/emoji/widget/EmojiExtractTextLayout.java
+++ b/emoji/core/src/android/support/text/emoji/widget/EmojiExtractTextLayout.java
@@ -17,11 +17,14 @@
 package android.support.text.emoji.widget;
 
 import android.content.Context;
+import android.content.res.TypedArray;
 import android.inputmethodservice.InputMethodService;
 import android.os.Build;
 import android.support.annotation.NonNull;
 import android.support.annotation.Nullable;
 import android.support.annotation.RequiresApi;
+import android.support.text.emoji.EmojiCompat;
+import android.support.text.emoji.EmojiSpan;
 import android.support.text.emoji.R;
 import android.text.InputType;
 import android.util.AttributeSet;
@@ -55,44 +58,48 @@
  *     }
  * }
  * </pre>
+ *
+ * @attr ref android.support.text.emoji.R.styleable#EmojiExtractTextLayout_emojiReplaceStrategy
  */
 public class EmojiExtractTextLayout extends LinearLayout {
 
     private ExtractButtonCompat mExtractAction;
+    private EmojiExtractEditText mExtractEditText;
     private ViewGroup mExtractAccessories;
     private View.OnClickListener mButtonOnClickListener;
 
     /**
-     * Prevent calling {@link #init(Context)} multiple times in case super() constructors
-     * call other constructors.
+     * Prevent calling {@link #init(Context, AttributeSet, int)}} multiple times in case super()
+     * constructors call other constructors.
      */
     private boolean mInitialized;
 
     public EmojiExtractTextLayout(Context context) {
         super(context);
-        init(context);
+        init(context, null /*attrs*/, 0 /*defStyleAttr*/, 0 /*defStyleRes*/);
     }
 
     public EmojiExtractTextLayout(Context context,
             @Nullable AttributeSet attrs) {
         super(context, attrs);
-        init(context);
+        init(context, attrs, 0 /*defStyleAttr*/, 0 /*defStyleRes*/);
     }
 
     public EmojiExtractTextLayout(Context context,
             @Nullable AttributeSet attrs, int defStyleAttr) {
         super(context, attrs, defStyleAttr);
-        init(context);
+        init(context, attrs, defStyleAttr, 0 /*defStyleRes*/);
     }
 
     @RequiresApi(api = Build.VERSION_CODES.LOLLIPOP)
     public EmojiExtractTextLayout(Context context, AttributeSet attrs,
             int defStyleAttr, int defStyleRes) {
         super(context, attrs, defStyleAttr, defStyleRes);
-        init(context);
+        init(context, attrs, defStyleAttr, defStyleRes);
     }
 
-    private void init(@NonNull Context context) {
+    private void init(@NonNull Context context, @Nullable AttributeSet attrs, int defStyleAttr,
+            int defStyleRes) {
         if (!mInitialized) {
             mInitialized = true;
             setOrientation(HORIZONTAL);
@@ -101,10 +108,49 @@
                             true /*attachToRoot*/);
             mExtractAccessories = view.findViewById(R.id.inputExtractAccessories);
             mExtractAction = view.findViewById(R.id.inputExtractAction);
+            mExtractEditText = view.findViewById(android.R.id.inputExtractEditText);
+
+            if (attrs != null) {
+                final TypedArray a = context.obtainStyledAttributes(attrs,
+                        R.styleable.EmojiExtractTextLayout, defStyleAttr, defStyleRes);
+                final int replaceStrategy = a.getInteger(
+                        R.styleable.EmojiExtractTextLayout_emojiReplaceStrategy,
+                        EmojiCompat.REPLACE_STRATEGY_DEFAULT);
+                mExtractEditText.setEmojiReplaceStrategy(replaceStrategy);
+                a.recycle();
+            }
         }
     }
 
     /**
+     * Sets whether to replace all emoji with {@link EmojiSpan}s. Default value is
+     * {@link EmojiCompat#REPLACE_STRATEGY_DEFAULT}.
+     *
+     * @param replaceStrategy should be one of {@link EmojiCompat#REPLACE_STRATEGY_DEFAULT},
+     *                        {@link EmojiCompat#REPLACE_STRATEGY_NON_EXISTENT},
+     *                        {@link EmojiCompat#REPLACE_STRATEGY_ALL}
+     *
+     * @attr ref android.support.text.emoji.R.styleable#EmojiExtractTextLayout_emojiReplaceStrategy
+     */
+    public void setEmojiReplaceStrategy(@EmojiCompat.ReplaceStrategy int replaceStrategy) {
+        mExtractEditText.setEmojiReplaceStrategy(replaceStrategy);
+    }
+
+    /**
+     * Returns whether to replace all emoji with {@link EmojiSpan}s. Default value is
+     * {@link EmojiCompat#REPLACE_STRATEGY_DEFAULT}.
+     *
+     * @return one of {@link EmojiCompat#REPLACE_STRATEGY_DEFAULT},
+     *                        {@link EmojiCompat#REPLACE_STRATEGY_NON_EXISTENT},
+     *                        {@link EmojiCompat#REPLACE_STRATEGY_ALL}
+     *
+     * @attr ref android.support.text.emoji.R.styleable#EmojiExtractTextLayout_emojiReplaceStrategy
+     */
+    public int getEmojiReplaceStrategy() {
+        return mExtractEditText.getEmojiReplaceStrategy();
+    }
+
+    /**
      * Initializes the layout. Call this function from
      * {@link InputMethodService#onUpdateExtractingViews(EditorInfo)
      * InputMethodService#onUpdateExtractingViews(EditorInfo)}.
diff --git a/emoji/core/src/android/support/text/emoji/widget/EmojiTextWatcher.java b/emoji/core/src/android/support/text/emoji/widget/EmojiTextWatcher.java
index ee92bd5..de3aea1 100644
--- a/emoji/core/src/android/support/text/emoji/widget/EmojiTextWatcher.java
+++ b/emoji/core/src/android/support/text/emoji/widget/EmojiTextWatcher.java
@@ -40,6 +40,8 @@
     private final EditText mEditText;
     private InitCallback mInitCallback;
     private int mMaxEmojiCount = EditTextAttributeHelper.MAX_EMOJI_COUNT;
+    @EmojiCompat.ReplaceStrategy
+    private int mEmojiReplaceStrategy = EmojiCompat.REPLACE_STRATEGY_DEFAULT;
 
     EmojiTextWatcher(EditText editText) {
         mEditText = editText;
@@ -53,6 +55,14 @@
         return mMaxEmojiCount;
     }
 
+    @EmojiCompat.ReplaceStrategy int getEmojiReplaceStrategy() {
+        return mEmojiReplaceStrategy;
+    }
+
+    void setEmojiReplaceStrategy(@EmojiCompat.ReplaceStrategy int replaceStrategy) {
+        mEmojiReplaceStrategy = replaceStrategy;
+    }
+
     @Override
     public void onTextChanged(CharSequence charSequence, final int start, final int before,
             final int after) {
@@ -65,7 +75,8 @@
             switch (EmojiCompat.get().getLoadState()){
                 case EmojiCompat.LOAD_STATE_SUCCEEDED:
                     final Spannable s = (Spannable) charSequence;
-                    EmojiCompat.get().process(s, start, start + after, mMaxEmojiCount);
+                    EmojiCompat.get().process(s, start, start + after, mMaxEmojiCount,
+                            mEmojiReplaceStrategy);
                     break;
                 case EmojiCompat.LOAD_STATE_LOADING:
                     EmojiCompat.get().registerInitCallback(getInitCallback());
diff --git a/emoji/core/tests/java/android/support/text/emoji/EmojiCompatTest.java b/emoji/core/tests/java/android/support/text/emoji/EmojiCompatTest.java
index a656cb0..ee31ed7 100644
--- a/emoji/core/tests/java/android/support/text/emoji/EmojiCompatTest.java
+++ b/emoji/core/tests/java/android/support/text/emoji/EmojiCompatTest.java
@@ -643,6 +643,21 @@
     }
 
     @Test
+    @SdkSuppress(maxSdkVersion = 18)
+    public void testGetAssetSignature() {
+        final String signature = EmojiCompat.get().getAssetSignature();
+        assertTrue(signature.isEmpty());
+    }
+
+    @Test
+    @SdkSuppress(minSdkVersion = 19)
+    public void testGetAssetSignature_api19() {
+        final String signature = EmojiCompat.get().getAssetSignature();
+        assertNotNull(signature);
+        assertFalse(signature.isEmpty());
+    }
+
+    @Test
     @SdkSuppress(minSdkVersion = 19)
     public void testUpdateEditorInfoAttrs_setsKeysIfInitialized() {
         final EditorInfo editorInfo = new EditorInfo();
diff --git a/emoji/core/tests/java/android/support/text/emoji/util/EmojiMatcher.java b/emoji/core/tests/java/android/support/text/emoji/util/EmojiMatcher.java
index 8e871e0..2915acd 100644
--- a/emoji/core/tests/java/android/support/text/emoji/util/EmojiMatcher.java
+++ b/emoji/core/tests/java/android/support/text/emoji/util/EmojiMatcher.java
@@ -65,8 +65,8 @@
         return argThat(new ArgumentMatcher<T>() {
             @Override
             public boolean matches(T o) {
-                if (o instanceof CharSequence && expected.getClass() == o.getClass()) {
-                    return TextUtils.equals(expected, (CharSequence) o);
+                if (o instanceof CharSequence) {
+                    return TextUtils.equals(expected, o);
                 }
                 return false;
             }
diff --git a/emoji/core/tests/java/android/support/text/emoji/widget/EmojiEditTextHelperTest.java b/emoji/core/tests/java/android/support/text/emoji/widget/EmojiEditTextHelperTest.java
index a2856be..efc7ca0 100644
--- a/emoji/core/tests/java/android/support/text/emoji/widget/EmojiEditTextHelperTest.java
+++ b/emoji/core/tests/java/android/support/text/emoji/widget/EmojiEditTextHelperTest.java
@@ -123,6 +123,32 @@
 
         mEmojiEditTextHelper.setMaxEmojiCount(1);
 
-        assertEquals(emojiTextWatcher.getMaxEmojiCount(), 1);
+        assertEquals(1, emojiTextWatcher.getMaxEmojiCount());
     }
+
+    @Test
+    public void testSetEmojiReplaceStrategy() {
+        mEditText = mock(EditText.class);
+        mEmojiEditTextHelper = new EmojiEditTextHelper(mEditText);
+
+        //assert the default value
+        assertEquals(EmojiCompat.REPLACE_STRATEGY_DEFAULT,
+                mEmojiEditTextHelper.getEmojiReplaceStrategy());
+
+        // capture TextWatcher
+        final ArgumentCaptor<TextWatcher> argumentCaptor = ArgumentCaptor.forClass(
+                TextWatcher.class);
+        verify(mEditText, times(1)).addTextChangedListener(argumentCaptor.capture());
+        assertThat(argumentCaptor.getValue(), instanceOf(EmojiTextWatcher.class));
+        final EmojiTextWatcher emojiTextWatcher = (EmojiTextWatcher) argumentCaptor.getValue();
+
+        mEmojiEditTextHelper.setEmojiReplaceStrategy(EmojiCompat.REPLACE_STRATEGY_NON_EXISTENT);
+
+        assertEquals(EmojiCompat.REPLACE_STRATEGY_NON_EXISTENT,
+                mEmojiEditTextHelper.getEmojiReplaceStrategy());
+
+        assertEquals(EmojiCompat.REPLACE_STRATEGY_NON_EXISTENT,
+                emojiTextWatcher.getEmojiReplaceStrategy());
+    }
+
 }
diff --git a/emoji/core/tests/java/android/support/text/emoji/widget/EmojiExtractTextLayoutTest.java b/emoji/core/tests/java/android/support/text/emoji/widget/EmojiExtractTextLayoutTest.java
index 4f929b9..8e1c6cf 100644
--- a/emoji/core/tests/java/android/support/text/emoji/widget/EmojiExtractTextLayoutTest.java
+++ b/emoji/core/tests/java/android/support/text/emoji/widget/EmojiExtractTextLayoutTest.java
@@ -16,18 +16,25 @@
 
 package android.support.text.emoji.widget;
 
+import static android.support.text.emoji.util.EmojiMatcher.sameCharSequence;
+
 import static junit.framework.TestCase.assertFalse;
 
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertNotNull;
 import static org.junit.Assert.assertTrue;
+import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.ArgumentMatchers.eq;
 import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
 import static org.mockito.Mockito.when;
 
 import android.content.Context;
 import android.inputmethodservice.InputMethodService;
 import android.support.test.InstrumentationRegistry;
 import android.support.test.annotation.UiThreadTest;
+import android.support.test.filters.SdkSuppress;
 import android.support.test.filters.SmallTest;
 import android.support.test.runner.AndroidJUnit4;
 import android.support.text.emoji.EmojiCompat;
@@ -39,18 +46,23 @@
 import android.view.inputmethod.EditorInfo;
 
 import org.junit.Before;
+import org.junit.BeforeClass;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 
 @SmallTest
 @RunWith(AndroidJUnit4.class)
 public class EmojiExtractTextLayoutTest {
+
     private InputMethodService mInputMethodService;
-    private EmojiExtractTextLayout mLayout;
+
+    @BeforeClass
+    public static void setupEmojiCompat() {
+        EmojiCompat.reset(mock(EmojiCompat.class));
+    }
 
     @Before
     public void setup() {
-        EmojiCompat.reset(mock(EmojiCompat.class));
         mInputMethodService = mock(InputMethodService.class);
     }
 
@@ -58,14 +70,14 @@
     @UiThreadTest
     public void testInflate() {
         final Context context = InstrumentationRegistry.getTargetContext();
-        mLayout = (EmojiExtractTextLayout) LayoutInflater.from(context)
+        final EmojiExtractTextLayout layout = (EmojiExtractTextLayout) LayoutInflater.from(context)
                 .inflate(android.support.text.emoji.test.R.layout.extract_view, null);
 
-        final EmojiExtractEditText extractEditText = mLayout.findViewById(
+        final EmojiExtractEditText extractEditText = layout.findViewById(
                 android.R.id.inputExtractEditText);
         assertNotNull(extractEditText);
 
-        final ViewGroup inputExtractAccessories = mLayout.findViewById(
+        final ViewGroup inputExtractAccessories = layout.findViewById(
                 R.id.inputExtractAccessories);
         assertNotNull(inputExtractAccessories);
 
@@ -76,9 +88,56 @@
 
     @Test
     @UiThreadTest
+    public void testSetEmojiReplaceStrategy() {
+        final Context context = InstrumentationRegistry.getTargetContext();
+
+        final EmojiExtractTextLayout layout = (EmojiExtractTextLayout) LayoutInflater.from(context)
+                .inflate(android.support.text.emoji.test.R.layout.extract_view_with_attrs, null);
+
+        assertEquals(EmojiCompat.REPLACE_STRATEGY_NON_EXISTENT, layout.getEmojiReplaceStrategy());
+
+        final EmojiExtractEditText extractEditText = layout.findViewById(
+                android.R.id.inputExtractEditText);
+        assertNotNull(extractEditText);
+        assertEquals(EmojiCompat.REPLACE_STRATEGY_NON_EXISTENT,
+                extractEditText.getEmojiReplaceStrategy());
+
+        layout.setEmojiReplaceStrategy(EmojiCompat.REPLACE_STRATEGY_ALL);
+        assertEquals(EmojiCompat.REPLACE_STRATEGY_ALL, layout.getEmojiReplaceStrategy());
+        assertEquals(EmojiCompat.REPLACE_STRATEGY_ALL, extractEditText.getEmojiReplaceStrategy());
+    }
+
+    @Test
+    @UiThreadTest
+    @SdkSuppress(minSdkVersion = 19)
+    public void testSetEmojiReplaceStrategyCallEmojiCompatWithCorrectStrategy() {
+        final Context context = InstrumentationRegistry.getTargetContext();
+
+        final EmojiExtractTextLayout layout = (EmojiExtractTextLayout) LayoutInflater.from(context)
+                .inflate(android.support.text.emoji.test.R.layout.extract_view_with_attrs, null);
+
+        final EmojiExtractEditText extractEditText = layout.findViewById(
+                android.R.id.inputExtractEditText);
+        assertNotNull(layout);
+        assertNotNull(extractEditText);
+        assertEquals(EmojiCompat.REPLACE_STRATEGY_NON_EXISTENT, layout.getEmojiReplaceStrategy());
+
+        final EmojiCompat emojiCompat = mock(EmojiCompat.class);
+        when(emojiCompat.getLoadState()).thenReturn(EmojiCompat.LOAD_STATE_SUCCEEDED);
+        EmojiCompat.reset(emojiCompat);
+
+        final String testString = "anytext";
+        extractEditText.setText(testString);
+
+        verify(emojiCompat, times(1)).process(sameCharSequence(testString), anyInt(), anyInt(),
+                anyInt(), eq(EmojiCompat.REPLACE_STRATEGY_NON_EXISTENT));
+    }
+
+    @Test
+    @UiThreadTest
     public void testOnUpdateExtractingViews() {
         final Context context = InstrumentationRegistry.getTargetContext();
-        mLayout = (EmojiExtractTextLayout) LayoutInflater.from(context)
+        final EmojiExtractTextLayout layout = (EmojiExtractTextLayout) LayoutInflater.from(context)
                 .inflate(android.support.text.emoji.test.R.layout.extract_view, null);
 
         final EditorInfo editorInfo = new EditorInfo();
@@ -88,14 +147,14 @@
 
         when(mInputMethodService.isExtractViewShown()).thenReturn(true);
 
-        final ViewGroup inputExtractAccessories = mLayout.findViewById(
+        final ViewGroup inputExtractAccessories = layout.findViewById(
                 R.id.inputExtractAccessories);
         inputExtractAccessories.setVisibility(View.GONE);
 
         final ExtractButtonCompat extractButton = inputExtractAccessories.findViewById(
                 R.id.inputExtractAction);
 
-        mLayout.onUpdateExtractingViews(mInputMethodService, editorInfo);
+        layout.onUpdateExtractingViews(mInputMethodService, editorInfo);
 
         assertEquals(View.VISIBLE, inputExtractAccessories.getVisibility());
         assertEquals(editorInfo.actionLabel, extractButton.getText());
@@ -106,19 +165,19 @@
     @UiThreadTest
     public void testOnUpdateExtractingViews_hidesAccessoriesIfNoAction() {
         final Context context = InstrumentationRegistry.getTargetContext();
-        mLayout = (EmojiExtractTextLayout) LayoutInflater.from(context)
+        final EmojiExtractTextLayout layout = (EmojiExtractTextLayout) LayoutInflater.from(context)
                 .inflate(android.support.text.emoji.test.R.layout.extract_view, null);
 
         final EditorInfo editorInfo = new EditorInfo();
         editorInfo.imeOptions = EditorInfo.IME_ACTION_NONE;
         when(mInputMethodService.isExtractViewShown()).thenReturn(true);
 
-        final ViewGroup inputExtractAccessories = mLayout.findViewById(
+        final ViewGroup inputExtractAccessories = layout.findViewById(
                 R.id.inputExtractAccessories);
         final ExtractButtonCompat extractButton = inputExtractAccessories.findViewById(
                 R.id.inputExtractAction);
 
-        mLayout.onUpdateExtractingViews(mInputMethodService, editorInfo);
+        layout.onUpdateExtractingViews(mInputMethodService, editorInfo);
 
         assertEquals(View.GONE, inputExtractAccessories.getVisibility());
         assertFalse(extractButton.hasOnClickListeners());
diff --git a/emoji/core/tests/java/android/support/text/emoji/widget/EmojiTextWatcherTest.java b/emoji/core/tests/java/android/support/text/emoji/widget/EmojiTextWatcherTest.java
index 728947c..88549cf 100644
--- a/emoji/core/tests/java/android/support/text/emoji/widget/EmojiTextWatcherTest.java
+++ b/emoji/core/tests/java/android/support/text/emoji/widget/EmojiTextWatcherTest.java
@@ -18,6 +18,7 @@
 
 import static android.support.text.emoji.util.EmojiMatcher.sameCharSequence;
 
+import static org.junit.Assert.assertEquals;
 import static org.mockito.Matchers.any;
 import static org.mockito.Matchers.anyInt;
 import static org.mockito.Matchers.eq;
@@ -60,7 +61,7 @@
         mTextWatcher.onTextChanged(testString, 0, 0, 1);
 
         verify(mEmojiCompat, times(1)).process(sameCharSequence(testString), eq(0), eq(1),
-                eq(Integer.MAX_VALUE));
+                eq(Integer.MAX_VALUE), anyInt());
         verify(mEmojiCompat, times(0)).registerInitCallback(any(EmojiCompat.InitCallback.class));
     }
 
@@ -71,7 +72,8 @@
 
         mTextWatcher.onTextChanged(testString, 0, 0, 1);
 
-        verify(mEmojiCompat, times(0)).process(any(Spannable.class), anyInt(), anyInt(), anyInt());
+        verify(mEmojiCompat, times(0)).process(any(Spannable.class), anyInt(), anyInt(), anyInt(),
+                anyInt());
         verify(mEmojiCompat, times(1)).registerInitCallback(any(EmojiCompat.InitCallback.class));
     }
 
@@ -82,7 +84,28 @@
 
         mTextWatcher.onTextChanged(testString, 0, 0, 1);
 
-        verify(mEmojiCompat, times(0)).process(any(Spannable.class), anyInt(), anyInt(), anyInt());
+        verify(mEmojiCompat, times(0)).process(any(Spannable.class), anyInt(), anyInt(), anyInt(),
+                anyInt());
         verify(mEmojiCompat, times(0)).registerInitCallback(any(EmojiCompat.InitCallback.class));
     }
+
+    @Test
+    public void testSetEmojiReplaceStrategy() {
+        final Spannable testString = new SpannableString("abc");
+        when(mEmojiCompat.getLoadState()).thenReturn(EmojiCompat.LOAD_STATE_SUCCEEDED);
+
+        assertEquals(EmojiCompat.REPLACE_STRATEGY_DEFAULT, mTextWatcher.getEmojiReplaceStrategy());
+
+        mTextWatcher.onTextChanged(testString, 0, 0, 1);
+
+        verify(mEmojiCompat, times(1)).process(any(Spannable.class), anyInt(), anyInt(), anyInt(),
+                eq(EmojiCompat.REPLACE_STRATEGY_DEFAULT));
+
+        mTextWatcher.setEmojiReplaceStrategy(EmojiCompat.REPLACE_STRATEGY_ALL);
+
+        mTextWatcher.onTextChanged(testString, 0, 0, 1);
+
+        verify(mEmojiCompat, times(1)).process(any(Spannable.class), anyInt(), anyInt(), anyInt(),
+                eq(EmojiCompat.REPLACE_STRATEGY_ALL));
+    }
 }
diff --git a/emoji/core/tests/res/layout/extract_view_with_attrs.xml b/emoji/core/tests/res/layout/extract_view_with_attrs.xml
new file mode 100644
index 0000000..0c16e7d
--- /dev/null
+++ b/emoji/core/tests/res/layout/extract_view_with_attrs.xml
@@ -0,0 +1,25 @@
+<?xml version="1.0" encoding="utf-8"?>
+
+<!--
+  ~ Copyright (C) 2017 The Android Open Source Project
+  ~
+  ~ Licensed under the Apache License, Version 2.0 (the "License");
+  ~ you may not use this file except in compliance with the License.
+  ~ You may obtain a copy of the License at
+  ~
+  ~      http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing, software
+  ~ distributed under the License is distributed on an "AS IS" BASIS,
+  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  ~ See the License for the specific language governing permissions and
+  ~ limitations under the License.
+  -->
+
+<android.support.text.emoji.widget.EmojiExtractTextLayout
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:app="http://schemas.android.com/apk/res-auto"
+    android:id="@+id/emojiExtractTextLayout"
+    android:layout_width="match_parent"
+    android:layout_height="match_parent"
+    app:emojiReplaceStrategy="nonExistent"/>
\ No newline at end of file
diff --git a/exifinterface/lint-baseline.xml b/exifinterface/lint-baseline.xml
index 172bbf6..fb44511 100644
--- a/exifinterface/lint-baseline.xml
+++ b/exifinterface/lint-baseline.xml
@@ -1,4 +1,4 @@
 <?xml version="1.0" encoding="UTF-8"?>
-<issues format="4" by="lint 2.4.0-alpha6">
+<issues format="4" by="lint 3.0.0-alpha5">
 
 </issues>
diff --git a/exifinterface/src/android/support/media/ExifInterface.java b/exifinterface/src/android/support/media/ExifInterface.java
index 4a68dd7..4047ca7 100644
--- a/exifinterface/src/android/support/media/ExifInterface.java
+++ b/exifinterface/src/android/support/media/ExifInterface.java
@@ -50,6 +50,7 @@
 import java.util.Date;
 import java.util.HashMap;
 import java.util.HashSet;
+import java.util.List;
 import java.util.Map;
 import java.util.TimeZone;
 import java.util.concurrent.TimeUnit;
@@ -391,10 +392,15 @@
     public static final int ORIENTATION_FLIP_VERTICAL = 4;  // upside down mirror
     // flipped about top-left <--> bottom-right axis
     public static final int ORIENTATION_TRANSPOSE = 5;
-    public static final int ORIENTATION_ROTATE_90 = 6;  // rotate 90 cw to right it
+    public static final int ORIENTATION_ROTATE_90 = 6;  // rotate 90 degree clockwise
     // flipped about top-right <--> bottom-left axis
     public static final int ORIENTATION_TRANSVERSE = 7;
-    public static final int ORIENTATION_ROTATE_270 = 8;  // rotate 270 to right it
+    public static final int ORIENTATION_ROTATE_270 = 8;  // rotate 270 degree clockwise
+    private static final List<Integer> ROTATION_ORDER = Arrays.asList(ORIENTATION_NORMAL,
+            ORIENTATION_ROTATE_90, ORIENTATION_ROTATE_180, ORIENTATION_ROTATE_270);
+    private static final List<Integer> FLIPPED_ROTATION_ORDER = Arrays.asList(
+            ORIENTATION_FLIP_HORIZONTAL, ORIENTATION_TRANSVERSE, ORIENTATION_FLIP_VERTICAL,
+            ORIENTATION_TRANSPOSE);
 
     // Constants used for white balance
     public static final int WHITEBALANCE_AUTO = 0;
@@ -1433,7 +1439,7 @@
     }
 
     /**
-     * Set the value of the specified tag.
+     * Sets the value of the specified tag.
      *
      * @param tag the name of the tag.
      * @param value the value of the tag.
@@ -1573,6 +1579,122 @@
     }
 
     /**
+     * Resets the {@link #TAG_ORIENTATION} of the image to be {@link #ORIENTATION_NORMAL}.
+     */
+    public void resetOrientation() {
+        setAttribute(TAG_ORIENTATION, Integer.toString(ORIENTATION_NORMAL));
+    }
+
+    /**
+     * Rotates the image by the given degree clockwise. The degree should be a multiple of
+     * 90 (e.g, 90, 180, -90, etc.).
+     *
+     * @param degree The degree of rotation.
+     */
+    public void rotate(int degree) {
+        if (degree % 90 !=0) {
+            throw new IllegalArgumentException("degree should be a multiple of 90");
+        }
+
+        int currentOrientation = getAttributeInt(TAG_ORIENTATION, ORIENTATION_NORMAL);
+        int currentIndex, newIndex;
+        int resultOrientation;
+        if (ROTATION_ORDER.contains(currentOrientation)) {
+            currentIndex = ROTATION_ORDER.indexOf(currentOrientation);
+            newIndex = (currentIndex + degree / 90) % 4;
+            newIndex += newIndex < 0 ? 4 : 0;
+            resultOrientation = ROTATION_ORDER.get(newIndex);
+        } else if (FLIPPED_ROTATION_ORDER.contains(currentOrientation)) {
+            currentIndex = FLIPPED_ROTATION_ORDER.indexOf(currentOrientation);
+            newIndex = (currentIndex + degree / 90) % 4;
+            newIndex += newIndex < 0 ? 4 : 0;
+            resultOrientation = FLIPPED_ROTATION_ORDER.get(newIndex);
+        } else {
+            resultOrientation = ORIENTATION_UNDEFINED;
+        }
+
+        setAttribute(TAG_ORIENTATION, Integer.toString(resultOrientation));
+    }
+
+    /**
+     * Flips the image vertically.
+     */
+    public void flipVertically() {
+        int currentOrientation = getAttributeInt(TAG_ORIENTATION, ORIENTATION_NORMAL);
+        int resultOrientation;
+        switch (currentOrientation) {
+            case ORIENTATION_FLIP_HORIZONTAL:
+                resultOrientation = ORIENTATION_ROTATE_180;
+                break;
+            case ORIENTATION_ROTATE_180:
+                resultOrientation = ORIENTATION_FLIP_HORIZONTAL;
+                break;
+            case ORIENTATION_FLIP_VERTICAL:
+                resultOrientation = ORIENTATION_NORMAL;
+                break;
+            case ORIENTATION_TRANSPOSE:
+                resultOrientation = ORIENTATION_ROTATE_270;
+                break;
+            case ORIENTATION_ROTATE_90:
+                resultOrientation = ORIENTATION_TRANSVERSE;
+                break;
+            case ORIENTATION_TRANSVERSE:
+                resultOrientation = ORIENTATION_ROTATE_90;
+                break;
+            case ORIENTATION_ROTATE_270:
+                resultOrientation = ORIENTATION_TRANSPOSE;
+                break;
+            case ORIENTATION_NORMAL:
+                resultOrientation = ORIENTATION_FLIP_VERTICAL;
+                break;
+            case ORIENTATION_UNDEFINED:
+            default:
+                resultOrientation = ORIENTATION_UNDEFINED;
+                break;
+        }
+        setAttribute(TAG_ORIENTATION, Integer.toString(resultOrientation));
+    }
+
+    /**
+     * Flips the image horizontally.
+     */
+    public void flipHorizontally() {
+        int currentOrientation = getAttributeInt(TAG_ORIENTATION, ORIENTATION_NORMAL);
+        int resultOrientation;
+        switch (currentOrientation) {
+            case ORIENTATION_FLIP_HORIZONTAL:
+                resultOrientation = ORIENTATION_NORMAL;
+                break;
+            case ORIENTATION_ROTATE_180:
+                resultOrientation = ORIENTATION_FLIP_VERTICAL;
+                break;
+            case ORIENTATION_FLIP_VERTICAL:
+                resultOrientation = ORIENTATION_ROTATE_180;
+                break;
+            case ORIENTATION_TRANSPOSE:
+                resultOrientation = ORIENTATION_ROTATE_90;
+                break;
+            case ORIENTATION_ROTATE_90:
+                resultOrientation = ORIENTATION_TRANSPOSE;
+                break;
+            case ORIENTATION_TRANSVERSE:
+                resultOrientation = ORIENTATION_ROTATE_270;
+                break;
+            case ORIENTATION_ROTATE_270:
+                resultOrientation = ORIENTATION_TRANSVERSE;
+                break;
+            case ORIENTATION_NORMAL:
+                resultOrientation = ORIENTATION_FLIP_HORIZONTAL;
+                break;
+            case ORIENTATION_UNDEFINED:
+            default:
+                resultOrientation = ORIENTATION_UNDEFINED;
+                break;
+        }
+        setAttribute(TAG_ORIENTATION, Integer.toString(resultOrientation));
+    }
+
+    /**
      * Update the values of the tags in the tag groups if any value for the tag already was stored.
      *
      * @param tag the name of the tag.
diff --git a/exifinterface/tests/src/android/support/media/ExifInterfaceTest.java b/exifinterface/tests/src/android/support/media/ExifInterfaceTest.java
index 2223029..da6fdb9 100644
--- a/exifinterface/tests/src/android/support/media/ExifInterfaceTest.java
+++ b/exifinterface/tests/src/android/support/media/ExifInterfaceTest.java
@@ -83,6 +83,89 @@
                     263.34763236326, -1e10, 347.325252623, -4000.346323236};
     private static final double[] TEST_ALTITUDE_VALUES = new double[]
             {0, -2000, 10000, -355.99999999999, 18.02038};
+    private static final int[][] TEST_ROTATION_STATE_MACHINE = {
+            {ExifInterface.ORIENTATION_UNDEFINED, -90, ExifInterface.ORIENTATION_UNDEFINED},
+            {ExifInterface.ORIENTATION_UNDEFINED, 0, ExifInterface.ORIENTATION_UNDEFINED},
+            {ExifInterface.ORIENTATION_UNDEFINED, 90, ExifInterface.ORIENTATION_UNDEFINED},
+            {ExifInterface.ORIENTATION_UNDEFINED, 180, ExifInterface.ORIENTATION_UNDEFINED},
+            {ExifInterface.ORIENTATION_UNDEFINED, 270, ExifInterface.ORIENTATION_UNDEFINED},
+            {ExifInterface.ORIENTATION_UNDEFINED, 540, ExifInterface.ORIENTATION_UNDEFINED},
+            {ExifInterface.ORIENTATION_NORMAL, -90, ExifInterface.ORIENTATION_ROTATE_270},
+            {ExifInterface.ORIENTATION_NORMAL, 0, ExifInterface.ORIENTATION_NORMAL},
+            {ExifInterface.ORIENTATION_NORMAL, 90, ExifInterface.ORIENTATION_ROTATE_90},
+            {ExifInterface.ORIENTATION_NORMAL, 180,ExifInterface.ORIENTATION_ROTATE_180},
+            {ExifInterface.ORIENTATION_NORMAL, 270,ExifInterface.ORIENTATION_ROTATE_270},
+            {ExifInterface.ORIENTATION_NORMAL, 540,ExifInterface.ORIENTATION_ROTATE_180},
+            {ExifInterface.ORIENTATION_ROTATE_90, -90, ExifInterface.ORIENTATION_NORMAL},
+            {ExifInterface.ORIENTATION_ROTATE_90, 0, ExifInterface.ORIENTATION_ROTATE_90},
+            {ExifInterface.ORIENTATION_ROTATE_90, 90, ExifInterface.ORIENTATION_ROTATE_180},
+            {ExifInterface.ORIENTATION_ROTATE_90, 180,ExifInterface.ORIENTATION_ROTATE_270},
+            {ExifInterface.ORIENTATION_ROTATE_90, 270,ExifInterface.ORIENTATION_NORMAL},
+            {ExifInterface.ORIENTATION_ROTATE_90, 540,ExifInterface.ORIENTATION_ROTATE_270},
+            {ExifInterface.ORIENTATION_ROTATE_180, -90, ExifInterface.ORIENTATION_ROTATE_90},
+            {ExifInterface.ORIENTATION_ROTATE_180, 0, ExifInterface.ORIENTATION_ROTATE_180},
+            {ExifInterface.ORIENTATION_ROTATE_180, 90, ExifInterface.ORIENTATION_ROTATE_270},
+            {ExifInterface.ORIENTATION_ROTATE_180, 180,ExifInterface.ORIENTATION_NORMAL},
+            {ExifInterface.ORIENTATION_ROTATE_180, 270,ExifInterface.ORIENTATION_ROTATE_90},
+            {ExifInterface.ORIENTATION_ROTATE_180, 540,ExifInterface.ORIENTATION_NORMAL},
+            {ExifInterface.ORIENTATION_ROTATE_270, -90, ExifInterface.ORIENTATION_ROTATE_180},
+            {ExifInterface.ORIENTATION_ROTATE_270, 0, ExifInterface.ORIENTATION_ROTATE_270},
+            {ExifInterface.ORIENTATION_ROTATE_270, 90, ExifInterface.ORIENTATION_NORMAL},
+            {ExifInterface.ORIENTATION_ROTATE_270, 180,ExifInterface.ORIENTATION_ROTATE_90},
+            {ExifInterface.ORIENTATION_ROTATE_270, 270,ExifInterface.ORIENTATION_ROTATE_180},
+            {ExifInterface.ORIENTATION_ROTATE_270, 540,ExifInterface.ORIENTATION_ROTATE_90},
+            {ExifInterface.ORIENTATION_FLIP_VERTICAL, -90, ExifInterface.ORIENTATION_TRANSVERSE},
+            {ExifInterface.ORIENTATION_FLIP_VERTICAL, 0, ExifInterface.ORIENTATION_FLIP_VERTICAL},
+            {ExifInterface.ORIENTATION_FLIP_VERTICAL, 90, ExifInterface.ORIENTATION_TRANSPOSE},
+            {ExifInterface.ORIENTATION_FLIP_VERTICAL, 180,
+                    ExifInterface.ORIENTATION_FLIP_HORIZONTAL},
+            {ExifInterface.ORIENTATION_FLIP_VERTICAL, 270,ExifInterface.ORIENTATION_TRANSVERSE},
+            {ExifInterface.ORIENTATION_FLIP_VERTICAL, 540,
+                    ExifInterface.ORIENTATION_FLIP_HORIZONTAL},
+            {ExifInterface.ORIENTATION_FLIP_HORIZONTAL, -90, ExifInterface.ORIENTATION_TRANSPOSE},
+            {ExifInterface.ORIENTATION_FLIP_HORIZONTAL, 0,
+                    ExifInterface.ORIENTATION_FLIP_HORIZONTAL},
+            {ExifInterface.ORIENTATION_FLIP_HORIZONTAL, 90, ExifInterface.ORIENTATION_TRANSVERSE},
+            {ExifInterface.ORIENTATION_FLIP_HORIZONTAL, 180,
+                    ExifInterface.ORIENTATION_FLIP_VERTICAL},
+            {ExifInterface.ORIENTATION_FLIP_HORIZONTAL, 270, ExifInterface.ORIENTATION_TRANSPOSE},
+            {ExifInterface.ORIENTATION_FLIP_HORIZONTAL, 540,
+                    ExifInterface.ORIENTATION_FLIP_VERTICAL},
+            {ExifInterface.ORIENTATION_TRANSPOSE, -90, ExifInterface.ORIENTATION_FLIP_VERTICAL},
+            {ExifInterface.ORIENTATION_TRANSPOSE, 0, ExifInterface.ORIENTATION_TRANSPOSE},
+            {ExifInterface.ORIENTATION_TRANSPOSE, 90, ExifInterface.ORIENTATION_FLIP_HORIZONTAL},
+            {ExifInterface.ORIENTATION_TRANSPOSE, 180,ExifInterface.ORIENTATION_TRANSVERSE},
+            {ExifInterface.ORIENTATION_TRANSPOSE, 270,ExifInterface.ORIENTATION_FLIP_VERTICAL},
+            {ExifInterface.ORIENTATION_TRANSPOSE, 540,ExifInterface.ORIENTATION_TRANSVERSE},
+            {ExifInterface.ORIENTATION_TRANSVERSE, -90, ExifInterface.ORIENTATION_FLIP_HORIZONTAL},
+            {ExifInterface.ORIENTATION_TRANSVERSE, 0, ExifInterface.ORIENTATION_TRANSVERSE},
+            {ExifInterface.ORIENTATION_TRANSVERSE, 90, ExifInterface.ORIENTATION_FLIP_VERTICAL},
+            {ExifInterface.ORIENTATION_TRANSVERSE, 180,ExifInterface.ORIENTATION_TRANSPOSE},
+            {ExifInterface.ORIENTATION_TRANSVERSE, 270,ExifInterface.ORIENTATION_FLIP_HORIZONTAL},
+            {ExifInterface.ORIENTATION_TRANSVERSE, 540,ExifInterface.ORIENTATION_TRANSPOSE},
+    };
+    private static final int[][] TEST_FLIP_VERTICALLY_STATE_MACHINE = {
+            {ExifInterface.ORIENTATION_UNDEFINED, ExifInterface.ORIENTATION_UNDEFINED},
+            {ExifInterface.ORIENTATION_NORMAL, ExifInterface.ORIENTATION_FLIP_VERTICAL},
+            {ExifInterface.ORIENTATION_ROTATE_90, ExifInterface.ORIENTATION_TRANSVERSE},
+            {ExifInterface.ORIENTATION_ROTATE_180, ExifInterface.ORIENTATION_FLIP_HORIZONTAL},
+            {ExifInterface.ORIENTATION_ROTATE_270, ExifInterface.ORIENTATION_TRANSPOSE},
+            {ExifInterface.ORIENTATION_FLIP_VERTICAL, ExifInterface.ORIENTATION_NORMAL},
+            {ExifInterface.ORIENTATION_FLIP_HORIZONTAL, ExifInterface.ORIENTATION_ROTATE_180},
+            {ExifInterface.ORIENTATION_TRANSPOSE, ExifInterface.ORIENTATION_ROTATE_270},
+            {ExifInterface.ORIENTATION_TRANSVERSE, ExifInterface.ORIENTATION_ROTATE_90}
+    };
+    private static final int[][] TEST_FLIP_HORIZONTALLY_STATE_MACHINE = {
+            {ExifInterface.ORIENTATION_UNDEFINED, ExifInterface.ORIENTATION_UNDEFINED},
+            {ExifInterface.ORIENTATION_NORMAL, ExifInterface.ORIENTATION_FLIP_HORIZONTAL},
+            {ExifInterface.ORIENTATION_ROTATE_90, ExifInterface.ORIENTATION_TRANSPOSE},
+            {ExifInterface.ORIENTATION_ROTATE_180, ExifInterface.ORIENTATION_FLIP_VERTICAL},
+            {ExifInterface.ORIENTATION_ROTATE_270, ExifInterface.ORIENTATION_TRANSVERSE},
+            {ExifInterface.ORIENTATION_FLIP_VERTICAL, ExifInterface.ORIENTATION_ROTATE_180},
+            {ExifInterface.ORIENTATION_FLIP_HORIZONTAL, ExifInterface.ORIENTATION_NORMAL},
+            {ExifInterface.ORIENTATION_TRANSPOSE, ExifInterface.ORIENTATION_ROTATE_90},
+            {ExifInterface.ORIENTATION_TRANSVERSE, ExifInterface.ORIENTATION_ROTATE_270}
+    };
 
     private static final String[] EXIF_TAGS = {
             ExifInterface.TAG_MAKE,
@@ -376,6 +459,67 @@
         assertEquals(currentTimeStamp, exif.getDateTime());
     }
 
+    @Test
+    @SmallTest
+    public void testRotation() throws IOException {
+        File imageFile = new File(
+                Environment.getExternalStorageDirectory(), EXIF_BYTE_ORDER_II_JPEG);
+        ExifInterface exif = new ExifInterface(imageFile.getAbsolutePath());
+
+        int num;
+        // Test flip vertically.
+        for (num = 0; num < TEST_FLIP_VERTICALLY_STATE_MACHINE.length; num++) {
+            exif.setAttribute(ExifInterface.TAG_ORIENTATION,
+                    Integer.toString(TEST_FLIP_VERTICALLY_STATE_MACHINE[num][0]));
+            exif.flipVertically();
+            exif.saveAttributes();
+            exif = new ExifInterface(imageFile.getAbsolutePath());
+            assertIntTag(exif, ExifInterface.TAG_ORIENTATION,
+                    TEST_FLIP_VERTICALLY_STATE_MACHINE[num][1]);
+
+        }
+
+        // Test flip horizontally.
+        for (num = 0; num < TEST_FLIP_VERTICALLY_STATE_MACHINE.length; num++) {
+            exif.setAttribute(ExifInterface.TAG_ORIENTATION,
+                    Integer.toString(TEST_FLIP_HORIZONTALLY_STATE_MACHINE[num][0]));
+            exif.flipHorizontally();
+            exif.saveAttributes();
+            exif = new ExifInterface(imageFile.getAbsolutePath());
+            assertIntTag(exif, ExifInterface.TAG_ORIENTATION,
+                    TEST_FLIP_HORIZONTALLY_STATE_MACHINE[num][1]);
+
+        }
+
+        // Test rotate by degrees
+        exif.setAttribute(ExifInterface.TAG_ORIENTATION,
+                Integer.toString(ExifInterface.ORIENTATION_NORMAL));
+        try {
+            exif.rotate(108);
+            fail("Rotate with 108 degree should throw IllegalArgumentException");
+        } catch (IllegalArgumentException e) {
+            // Success
+        }
+
+        for (num = 0; num < TEST_ROTATION_STATE_MACHINE.length; num++) {
+            exif.setAttribute(ExifInterface.TAG_ORIENTATION,
+                    Integer.toString(TEST_ROTATION_STATE_MACHINE[num][0]));
+            exif.rotate(TEST_ROTATION_STATE_MACHINE[num][1]);
+            exif.saveAttributes();
+            exif = new ExifInterface(imageFile.getAbsolutePath());
+            assertIntTag(exif, ExifInterface.TAG_ORIENTATION, TEST_ROTATION_STATE_MACHINE[num][2]);
+        }
+
+        // Test reset the rotation.
+        exif.setAttribute(ExifInterface.TAG_ORIENTATION,
+                Integer.toString(ExifInterface.ORIENTATION_FLIP_HORIZONTAL));
+        exif.resetOrientation();
+        exif.saveAttributes();
+        exif = new ExifInterface(imageFile.getAbsolutePath());
+        assertIntTag(exif, ExifInterface.TAG_ORIENTATION, ExifInterface.ORIENTATION_NORMAL);
+
+    }
+
     private void printExifTagsAndValues(String fileName, ExifInterface exifInterface) {
         // Prints thumbnail information.
         if (exifInterface.hasThumbnail()) {
diff --git a/fragment/java/android/support/v4/app/FragmentStatePagerAdapter.java b/fragment/java/android/support/v4/app/FragmentStatePagerAdapter.java
index 7aa3d20..fc27c4f 100644
--- a/fragment/java/android/support/v4/app/FragmentStatePagerAdapter.java
+++ b/fragment/java/android/support/v4/app/FragmentStatePagerAdapter.java
@@ -62,7 +62,7 @@
  *      complete}
  */
 public abstract class FragmentStatePagerAdapter extends PagerAdapter {
-    private static final String TAG = "FragmentStatePagerAdapter";
+    private static final String TAG = "FragmentStatePagerAdapt";
     private static final boolean DEBUG = false;
 
     private final FragmentManager mFragmentManager;
diff --git a/fragment/lint-baseline.xml b/fragment/lint-baseline.xml
index 2a79d28..b373b5e 100644
--- a/fragment/lint-baseline.xml
+++ b/fragment/lint-baseline.xml
@@ -1,191 +1,15 @@
 <?xml version="1.0" encoding="UTF-8"?>
-<issues format="4" by="lint 2.4.0-alpha6">
-
-    <issue
-        id="MissingSuperCall"
-        message="Overriding method should call `super.onAnimationEnd`"
-        errorLine1="                public void onAnimationEnd(Animation animation) {"
-        errorLine2="                            ~~~~~~~~~~~~~~">
-        <location
-            file="java/android/support/v4/app/FragmentManager.java"
-            line="1604"
-            column="29"/>
-    </issue>
-
-    <issue
-        id="MissingSuperCall"
-        message="Overriding method should call `super.onAnimationEnd`"
-        errorLine1="        public void onAnimationEnd(Animation animation) {"
-        errorLine2="                    ~~~~~~~~~~~~~~">
-        <location
-            file="java/android/support/v4/app/FragmentManager.java"
-            line="3935"
-            column="21"/>
-    </issue>
-
-    <issue
-        id="Range"
-        message="Value must be ≥ 0 (was -1)"
-        errorLine1="                childHeightSpec = MeasureSpec.makeMeasureSpec(maxLayoutHeight, MeasureSpec.AT_MOST);"
-        errorLine2="                                                              ~~~~~~~~~~~~~~~">
-        <location
-            file="java/android/support/v4/widget/SlidingPaneLayout.java"
-            line="536"
-            column="63"/>
-    </issue>
-
-    <issue
-        id="Range"
-        message="Value must be ≥ 0 (was -1)"
-        errorLine1="                childHeightSpec = MeasureSpec.makeMeasureSpec(maxLayoutHeight, MeasureSpec.EXACTLY);"
-        errorLine2="                                                              ~~~~~~~~~~~~~~~">
-        <location
-            file="java/android/support/v4/widget/SlidingPaneLayout.java"
-            line="538"
-            column="63"/>
-    </issue>
-
-    <issue
-        id="Range"
-        message="Value must be ≥ 0 (was -1)"
-        errorLine1="                                childHeightSpec = MeasureSpec.makeMeasureSpec(maxLayoutHeight,"
-        errorLine2="                                                                              ~~~~~~~~~~~~~~~">
-        <location
-            file="java/android/support/v4/widget/SlidingPaneLayout.java"
-            line="586"
-            column="79"/>
-    </issue>
-
-    <issue
-        id="Range"
-        message="Value must be ≥ 0 (was -1)"
-        errorLine1="                                childHeightSpec = MeasureSpec.makeMeasureSpec(maxLayoutHeight,"
-        errorLine2="                                                                              ~~~~~~~~~~~~~~~">
-        <location
-            file="java/android/support/v4/widget/SlidingPaneLayout.java"
-            line="589"
-            column="79"/>
-    </issue>
-
-    <issue
-        id="Range"
-        message="Value must be ≥ 0 (was -1)"
-        errorLine1="                            childHeightSpec = MeasureSpec.makeMeasureSpec(maxLayoutHeight,"
-        errorLine2="                                                                          ~~~~~~~~~~~~~~~">
-        <location
-            file="java/android/support/v4/widget/SlidingPaneLayout.java"
-            line="608"
-            column="75"/>
-    </issue>
-
-    <issue
-        id="Range"
-        message="Value must be ≥ 0 (was -1)"
-        errorLine1="                            childHeightSpec = MeasureSpec.makeMeasureSpec(maxLayoutHeight,"
-        errorLine2="                                                                          ~~~~~~~~~~~~~~~">
-        <location
-            file="java/android/support/v4/widget/SlidingPaneLayout.java"
-            line="611"
-            column="75"/>
-    </issue>
-
-    <issue
-        id="LongLogTag"
-        message="The logging tag can be at most 23 characters, was 25 (FragmentStatePagerAdapter)"
-        errorLine1="        if (DEBUG) Log.v(TAG, &quot;Adding item #&quot; + position + &quot;: f=&quot; + fragment);"
-        errorLine2="                         ~~~">
-        <location
-            file="java/android/support/v4/app/FragmentStatePagerAdapter.java"
-            line="110"
-            column="26"/>
-    </issue>
-
-    <issue
-        id="LongLogTag"
-        message="The logging tag can be at most 23 characters, was 25 (FragmentStatePagerAdapter)"
-        errorLine1="        if (DEBUG) Log.v(TAG, &quot;Removing item #&quot; + position + &quot;: f=&quot; + object"
-        errorLine2="                         ~~~">
-        <location
-            file="java/android/support/v4/app/FragmentStatePagerAdapter.java"
-            line="135"
-            column="26"/>
-    </issue>
-
-    <issue
-        id="LongLogTag"
-        message="The logging tag can be at most 23 characters, was 25 (FragmentStatePagerAdapter)"
-        errorLine1="                        Log.w(TAG, &quot;Bad fragment at key &quot; + key);"
-        errorLine2="                              ~~~">
-        <location
-            file="java/android/support/v4/app/FragmentStatePagerAdapter.java"
-            line="224"
-            column="31"/>
-    </issue>
-
-    <issue
-        id="LongLogTag"
-        message="The logging tag can be at most 23 characters, was 28 (RoundedBitmapDrawableFactory)"
-        errorLine1="            Log.w(TAG, &quot;RoundedBitmapDrawable cannot decode &quot; + filepath);"
-        errorLine2="                  ~~~">
-        <location
-            file="java/android/support/v4/graphics/drawable/RoundedBitmapDrawableFactory.java"
-            line="80"
-            column="19"/>
-    </issue>
-
-    <issue
-        id="LongLogTag"
-        message="The logging tag can be at most 23 characters, was 28 (RoundedBitmapDrawableFactory)"
-        errorLine1="            Log.w(TAG, &quot;RoundedBitmapDrawable cannot decode &quot; + is);"
-        errorLine2="                  ~~~">
-        <location
-            file="java/android/support/v4/graphics/drawable/RoundedBitmapDrawableFactory.java"
-            line="93"
-            column="19"/>
-    </issue>
-
-    <issue
-        id="LongLogTag"
-        message="The logging tag can be at most 23 characters, was 24 (WakefulBroadcastReceiver)"
-        errorLine1="            Log.w(&quot;WakefulBroadcastReceiver&quot;, &quot;No active wake lock id #&quot; + id);"
-        errorLine2="                  ~~~~~~~~~~~~~~~~~~~~~~~~~~">
-        <location
-            file="java/android/support/v4/content/WakefulBroadcastReceiver.java"
-            line="141"
-            column="19"/>
-    </issue>
+<issues format="4" by="lint 3.0.0-alpha5">
 
     <issue
         id="WrongConstant"
-        message="Must be one of: Gravity.LEFT, Gravity.RIGHT, GravityCompat.START, GravityCompat.END"
-        errorLine1="        setDrawerLockMode(lockMode, gravity);"
-        errorLine2="                                    ~~~~~~~">
+        message="Must be one or more of: Gravity.LEFT, Gravity.RIGHT, GravityCompat.START, GravityCompat.END"
+        errorLine1="                            + gravityToString(childGravity) + &quot; but this &quot; + TAG + &quot; already has a &quot;"
+        errorLine2="                                              ~~~~~~~~~~~~">
         <location
             file="java/android/support/v4/widget/DrawerLayout.java"
-            line="680"
-            column="37"/>
-    </issue>
-
-    <issue
-        id="WrongConstant"
-        message="Must be one of: Gravity.LEFT, Gravity.RIGHT, GravityCompat.START, GravityCompat.END"
-        errorLine1="        return getDrawerLockMode(drawerGravity);"
-        errorLine2="                                 ~~~~~~~~~~~~~">
-        <location
-            file="java/android/support/v4/widget/DrawerLayout.java"
-            line="753"
-            column="34"/>
-    </issue>
-
-    <issue
-        id="WrongConstant"
-        message="Flag not allowed here"
-        errorLine1="                        getDrawerViewAbsoluteGravity(child) &amp; Gravity.HORIZONTAL_GRAVITY_MASK;"
-        errorLine2="                        ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
-        <location
-            file="java/android/support/v4/widget/DrawerLayout.java"
-            line="1086"
-            column="25"/>
+            line="1075"
+            column="47"/>
     </issue>
 
 </issues>
diff --git a/gradle.properties b/gradle.properties
index d1c2c14..9b77c57 100644
--- a/gradle.properties
+++ b/gradle.properties
@@ -1,4 +1,6 @@
 org.gradle.jvmargs=-Xmx4g
 org.gradle.daemon=true
-org.gradle.configureondemand=true
-org.gradle.parallel=true
\ No newline at end of file
+# Disabled due to b/63329112
+# org.gradle.configureondemand=true
+org.gradle.parallel=true
+android.useDexArchive=false
diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties
index 15afcfd..2a386c4 100644
--- a/gradle/wrapper/gradle-wrapper.properties
+++ b/gradle/wrapper/gradle-wrapper.properties
@@ -3,4 +3,4 @@
 distributionPath=wrapper/dists
 zipStoreBase=GRADLE_USER_HOME
 zipStorePath=wrapper/dists
-distributionUrl=../../../../tools/external/gradle/gradle-4.0-bin.zip
+distributionUrl=../../../../tools/external/gradle/gradle-4.1-milestone-1-bin.zip
diff --git a/graphics/drawable/animated/lint-baseline.xml b/graphics/drawable/animated/lint-baseline.xml
index a3f6f77..858c2c7 100644
--- a/graphics/drawable/animated/lint-baseline.xml
+++ b/graphics/drawable/animated/lint-baseline.xml
@@ -1,5 +1,5 @@
 <?xml version="1.0" encoding="UTF-8"?>
-<issues format="4" by="lint 2.4.0-alpha6">
+<issues format="4" by="lint 3.0.0-alpha5">
 
     <issue
         id="ResourceType"
@@ -13,124 +13,14 @@
     </issue>
 
     <issue
-        id="ResourceType"
-        message="Expected resource of type xml"
-        errorLine1="            final XmlPullParser parser = res.getXml(resId);"
-        errorLine2="                                                    ~~~~~">
-        <location
-            file="src/android/support/graphics/drawable/VectorDrawableCompat.java"
-            line="557"
-            column="53"/>
-    </issue>
-
-    <issue
-        id="Range"
-        message="Value must be ≥ 0 (was -1)"
-        errorLine1="                childHeightSpec = MeasureSpec.makeMeasureSpec(maxLayoutHeight, MeasureSpec.AT_MOST);"
-        errorLine2="                                                              ~~~~~~~~~~~~~~~">
-        <location
-            file="java/android/support/v4/widget/SlidingPaneLayout.java"
-            line="536"
-            column="63"/>
-    </issue>
-
-    <issue
-        id="Range"
-        message="Value must be ≥ 0 (was -1)"
-        errorLine1="                childHeightSpec = MeasureSpec.makeMeasureSpec(maxLayoutHeight, MeasureSpec.EXACTLY);"
-        errorLine2="                                                              ~~~~~~~~~~~~~~~">
-        <location
-            file="java/android/support/v4/widget/SlidingPaneLayout.java"
-            line="538"
-            column="63"/>
-    </issue>
-
-    <issue
-        id="Range"
-        message="Value must be ≥ 0 (was -1)"
-        errorLine1="                                childHeightSpec = MeasureSpec.makeMeasureSpec(maxLayoutHeight,"
-        errorLine2="                                                                              ~~~~~~~~~~~~~~~">
-        <location
-            file="java/android/support/v4/widget/SlidingPaneLayout.java"
-            line="586"
-            column="79"/>
-    </issue>
-
-    <issue
-        id="Range"
-        message="Value must be ≥ 0 (was -1)"
-        errorLine1="                                childHeightSpec = MeasureSpec.makeMeasureSpec(maxLayoutHeight,"
-        errorLine2="                                                                              ~~~~~~~~~~~~~~~">
-        <location
-            file="java/android/support/v4/widget/SlidingPaneLayout.java"
-            line="589"
-            column="79"/>
-    </issue>
-
-    <issue
-        id="Range"
-        message="Value must be ≥ 0 (was -1)"
-        errorLine1="                            childHeightSpec = MeasureSpec.makeMeasureSpec(maxLayoutHeight,"
-        errorLine2="                                                                          ~~~~~~~~~~~~~~~">
-        <location
-            file="java/android/support/v4/widget/SlidingPaneLayout.java"
-            line="608"
-            column="75"/>
-    </issue>
-
-    <issue
-        id="Range"
-        message="Value must be ≥ 0 (was -1)"
-        errorLine1="                            childHeightSpec = MeasureSpec.makeMeasureSpec(maxLayoutHeight,"
-        errorLine2="                                                                          ~~~~~~~~~~~~~~~">
-        <location
-            file="java/android/support/v4/widget/SlidingPaneLayout.java"
-            line="611"
-            column="75"/>
-    </issue>
-
-    <issue
         id="WrongConstant"
-        message="Must be one of: Gravity.LEFT, Gravity.RIGHT, GravityCompat.START, GravityCompat.END"
-        errorLine1="        setDrawerLockMode(lockMode, gravity);"
-        errorLine2="                                    ~~~~~~~">
+        message="Must be one or more of: Gravity.LEFT, Gravity.RIGHT, GravityCompat.START, GravityCompat.END"
+        errorLine1="                            + gravityToString(childGravity) + &quot; but this &quot; + TAG + &quot; already has a &quot;"
+        errorLine2="                                              ~~~~~~~~~~~~">
         <location
             file="java/android/support/v4/widget/DrawerLayout.java"
-            line="680"
-            column="37"/>
-    </issue>
-
-    <issue
-        id="WrongConstant"
-        message="Must be one of: Gravity.LEFT, Gravity.RIGHT, GravityCompat.START, GravityCompat.END"
-        errorLine1="        return getDrawerLockMode(drawerGravity);"
-        errorLine2="                                 ~~~~~~~~~~~~~">
-        <location
-            file="java/android/support/v4/widget/DrawerLayout.java"
-            line="753"
-            column="34"/>
-    </issue>
-
-    <issue
-        id="WrongConstant"
-        message="Flag not allowed here"
-        errorLine1="                        getDrawerViewAbsoluteGravity(child) &amp; Gravity.HORIZONTAL_GRAVITY_MASK;"
-        errorLine2="                        ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
-        <location
-            file="java/android/support/v4/widget/DrawerLayout.java"
-            line="1086"
-            column="25"/>
-    </issue>
-
-    <issue
-        id="WrongConstant"
-        message="Must be one of: View.LAYOUT_DIRECTION_LTR, View.LAYOUT_DIRECTION_RTL"
-        errorLine1="            return isAutoMirrored() &amp;&amp; getLayoutDirection() == LayoutDirection.RTL;"
-        errorLine2="                                                               ~~~~~~~~~~~~~~~~~~~">
-        <location
-            file="src/android/support/graphics/drawable/VectorDrawableCompat.java"
-            line="821"
-            column="64"/>
+            line="1075"
+            column="47"/>
     </issue>
 
 </issues>
diff --git a/graphics/drawable/static/lint-baseline.xml b/graphics/drawable/static/lint-baseline.xml
index 2809a1e..fb44511 100644
--- a/graphics/drawable/static/lint-baseline.xml
+++ b/graphics/drawable/static/lint-baseline.xml
@@ -1,26 +1,4 @@
 <?xml version="1.0" encoding="UTF-8"?>
-<issues format="4" by="lint 2.4.0-alpha6">
-
-    <issue
-        id="ResourceType"
-        message="Expected resource of type xml"
-        errorLine1="            final XmlPullParser parser = res.getXml(resId);"
-        errorLine2="                                                    ~~~~~">
-        <location
-            file="src/android/support/graphics/drawable/VectorDrawableCompat.java"
-            line="557"
-            column="53"/>
-    </issue>
-
-    <issue
-        id="WrongConstant"
-        message="Must be one of: View.LAYOUT_DIRECTION_LTR, View.LAYOUT_DIRECTION_RTL"
-        errorLine1="            return isAutoMirrored() &amp;&amp; getLayoutDirection() == LayoutDirection.RTL;"
-        errorLine2="                                                               ~~~~~~~~~~~~~~~~~~~">
-        <location
-            file="src/android/support/graphics/drawable/VectorDrawableCompat.java"
-            line="821"
-            column="64"/>
-    </issue>
+<issues format="4" by="lint 3.0.0-alpha5">
 
 </issues>
diff --git a/graphics/drawable/static/src/android/support/graphics/drawable/VectorDrawableCompat.java b/graphics/drawable/static/src/android/support/graphics/drawable/VectorDrawableCompat.java
index f9753eb..b68ef1b 100644
--- a/graphics/drawable/static/src/android/support/graphics/drawable/VectorDrawableCompat.java
+++ b/graphics/drawable/static/src/android/support/graphics/drawable/VectorDrawableCompat.java
@@ -16,6 +16,7 @@
 
 import static android.support.annotation.RestrictTo.Scope.LIBRARY_GROUP;
 
+import android.annotation.SuppressLint;
 import android.content.res.ColorStateList;
 import android.content.res.Resources;
 import android.content.res.Resources.Theme;
@@ -554,7 +555,7 @@
         }
 
         try {
-            final XmlPullParser parser = res.getXml(resId);
+            @SuppressLint("ResourceType") final XmlPullParser parser = res.getXml(resId);
             final AttributeSet attrs = Xml.asAttributeSet(parser);
             int type;
             while ((type = parser.next()) != XmlPullParser.START_TAG &&
@@ -818,7 +819,8 @@
     // We don't support RTL auto mirroring since the getLayoutDirection() is for API 17+.
     private boolean needMirroring() {
         if (Build.VERSION.SDK_INT >= 17) {
-            return isAutoMirrored() && getLayoutDirection() == LayoutDirection.RTL;
+            return isAutoMirrored()
+                    && DrawableCompat.getLayoutDirection(this) == LayoutDirection.RTL;
         } else {
             return false;
         }
diff --git a/lifecycle/.gitignore b/lifecycle/.gitignore
new file mode 100644
index 0000000..be4e6f1
--- /dev/null
+++ b/lifecycle/.gitignore
@@ -0,0 +1,4 @@
+local.properties
+maven-repo/
+build/
+*.DS_Store
diff --git a/lifecycle/common/build.gradle b/lifecycle/common/build.gradle
new file mode 100644
index 0000000..ac9b2e2f
--- /dev/null
+++ b/lifecycle/common/build.gradle
@@ -0,0 +1,14 @@
+apply plugin: 'java'
+apply plugin: 'maven'
+
+sourceCompatibility = 1.7
+
+dependencies {
+    testCompile libs.junit
+    testCompile libs.mockito_core
+    compile libs.support.annotations
+}
+
+archivesBaseName = "common"
+
+createAndroidCheckstyle(project)
diff --git a/lifecycle/common/src/main/java/android/arch/lifecycle/GenericLifecycleObserver.java b/lifecycle/common/src/main/java/android/arch/lifecycle/GenericLifecycleObserver.java
new file mode 100644
index 0000000..a34bfbf
--- /dev/null
+++ b/lifecycle/common/src/main/java/android/arch/lifecycle/GenericLifecycleObserver.java
@@ -0,0 +1,39 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.arch.lifecycle;
+
+/**
+ * Internal class that can receive any lifecycle change and dispatch it to the receiver.
+ * @hide
+ */
+@SuppressWarnings({"WeakerAccess", "unused"})
+public interface GenericLifecycleObserver extends LifecycleObserver {
+    /**
+     * Called when a state transition event happens.
+     *
+     * @param source The source of the event
+     * @param event The event
+     */
+    void onStateChanged(LifecycleOwner source, Lifecycle.Event event);
+
+    /**
+     * Returns the actual receiver of the event
+     *
+     * @return The actual receiver
+     */
+    Object getReceiver();
+}
diff --git a/lifecycle/common/src/main/java/android/arch/lifecycle/Lifecycle.java b/lifecycle/common/src/main/java/android/arch/lifecycle/Lifecycle.java
new file mode 100644
index 0000000..fcbd50a
--- /dev/null
+++ b/lifecycle/common/src/main/java/android/arch/lifecycle/Lifecycle.java
@@ -0,0 +1,196 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.arch.lifecycle;
+
+import android.support.annotation.MainThread;
+
+/**
+ * Defines an object that has an Android Lifecycle. {@link android.support.v4.app.Fragment Fragment}
+ * and {@link android.support.v4.app.FragmentActivity FragmentActivity} classes implement
+ * {@link LifecycleOwner} interface which has the {@link LifecycleOwner#getLifecycle()
+ * getLifecycle} method to access the Lifecycle. You can also implement {@link LifecycleOwner}
+ * in your own classes.
+ * <p>
+ * {@link Event#ON_CREATE}, {@link Event#ON_START}, {@link Event#ON_RESUME} events in this class
+ * are dispatched <b>after</b> the {@link LifecycleOwner}'s related method returns.
+ * {@link Event#ON_PAUSE}, {@link Event#ON_STOP}, {@link Event#ON_DESTROY} events in this class
+ * are dispatched <b>before</b> the {@link LifecycleOwner}'s related method is called.
+ * For instance, {@link Event#ON_START} will be dispatched after
+ * {@link android.app.Activity#onStart onStart} returns, {@link Event#ON_STOP} will be dispatched
+ * before {@link android.app.Activity#onStop onStop} is called.
+ * This gives you certain guarantees on which state the owner is in.
+ * <p>
+ * Lifecycle events are observed using annotations.
+ * <pre>
+ * class TestObserver implements LifecycleObserver {
+ *   {@literal @}OnLifecycleEvent(ON_STOP)
+ *   void onStopped() {}
+ * }
+ * </pre>
+ * <p>
+ * Multiple methods can observe the same event.
+ * <pre>
+ * class TestObserver implements LifecycleObserver {
+ *   {@literal @}OnLifecycleEvent(ON_STOP)
+ *   void onStoppedFirst() {}
+ *   {@literal @}OnLifecycleEvent(ON_STOP)
+ *   void onStoppedSecond() {}
+ * }
+ * </pre>
+ * <p>
+ * Observer methods can receive zero or one argument.
+ * If used, the first argument must be of type {@link LifecycleOwner}.
+ * Methods annotated with {@link Event#ON_ANY} can receive the second argument, which must be
+ * of type {@link Event}.
+ * <pre>
+ * class TestObserver implements LifecycleObserver {
+ *   {@literal @}OnLifecycleEvent(ON_CREATE)
+ *   void onCreated(LifecycleOwner source) {}
+ *   {@literal @}OnLifecycleEvent(ON_ANY)
+ *   void onAny(LifecycleOwner source, Event event) {}
+ * }
+ * </pre>
+ * These additional parameters are provided to allow you to conveniently observe multiple providers
+ * and events without tracking them manually.
+ */
+public abstract class Lifecycle {
+    /**
+     * Adds a LifecycleObserver that will be notified when the LifecycleOwner changes
+     * state.
+     * <p>
+     * The given observer will be brought to the current state of the LifecycleOwner.
+     * For example, if the LifecycleOwner is in {@link State#STARTED} state, the given observer
+     * will receive {@link Event#ON_CREATE}, {@link Event#ON_START} events.
+     *
+     * @param observer The observer to notify.
+     */
+    @MainThread
+    public abstract void addObserver(LifecycleObserver observer);
+
+    /**
+     * Removes the given observer from the observers list.
+     * <p>
+     * If this method is called while a state change is being dispatched,
+     * <ul>
+     * <li>If the given observer has not yet received that event, it will not receive it.
+     * <li>If the given observer has more than 1 method that observes the currently dispatched
+     * event and at least one of them received the event, all of them will receive the event and
+     * the removal will happen afterwards.
+     * </ul>
+     *
+     * @param observer The observer to be removed.
+     */
+    @MainThread
+    public abstract void removeObserver(LifecycleObserver observer);
+
+    /**
+     * Returns the current state of the Lifecycle.
+     *
+     * @return The current state of the Lifecycle.
+     */
+    @MainThread
+    public abstract State getCurrentState();
+
+    @SuppressWarnings("WeakerAccess")
+    public enum Event {
+        /**
+         * Constant for onCreate event of the {@link LifecycleOwner}.
+         */
+        ON_CREATE,
+        /**
+         * Constant for onStart event of the {@link LifecycleOwner}.
+         */
+        ON_START,
+        /**
+         * Constant for onResume event of the {@link LifecycleOwner}.
+         */
+        ON_RESUME,
+        /**
+         * Constant for onPause event of the {@link LifecycleOwner}.
+         */
+        ON_PAUSE,
+        /**
+         * Constant for onStop event of the {@link LifecycleOwner}.
+         */
+        ON_STOP,
+        /**
+         * Constant for onDestroy event of the {@link LifecycleOwner}.
+         */
+        ON_DESTROY,
+        /**
+         * An {@link Event Event} constant that can be used to match all events.
+         */
+        ON_ANY
+    }
+
+    /**
+     * Lifecycle states. You can consider the states as the nodes in a graph and
+     * {@link Event}s as the edges between these nodes.
+     */
+    @SuppressWarnings("WeakerAccess")
+    public enum State {
+        /**
+         * Destroyed state for a LifecycleOwner. After this event, this Lifecycle will not dispatch
+         * any more events. For instance, for an {@link android.app.Activity}, this state is reached
+         * <b>right before</b> Activity's {@link android.app.Activity#onDestroy() onDestroy} call.
+         */
+        DESTROYED,
+
+        /**
+         * Initialized state for a LifecycleOwner. For an {@link android.app.Activity}, this is
+         * the state when it is constructed but has not received
+         * {@link android.app.Activity#onCreate(android.os.Bundle) onCreate} yet.
+         */
+        INITIALIZED,
+
+        /**
+         * Created state for a LifecycleOwner. For an {@link android.app.Activity}, this state
+         * is reached in two cases:
+         * <ul>
+         *     <li>after {@link android.app.Activity#onCreate(android.os.Bundle) onCreate} call;
+         *     <li><b>right before</b> {@link android.app.Activity#onStop() onStop} call.
+         * </ul>
+         */
+        CREATED,
+
+        /**
+         * Started state for a LifecycleOwner. For an {@link android.app.Activity}, this state
+         * is reached in two cases:
+         * <ul>
+         *     <li>after {@link android.app.Activity#onStart() onStart} call;
+         *     <li><b>right before</b> {@link android.app.Activity#onPause() onPause} call.
+         * </ul>
+         */
+        STARTED,
+
+        /**
+         * Resumed state for a LifecycleOwner. For an {@link android.app.Activity}, this state
+         * is reached after {@link android.app.Activity#onResume() onResume} is called.
+         */
+        RESUMED;
+
+        /**
+         * Compares if this State is greater or equal to the given {@code state}.
+         *
+         * @param state State to compare with
+         * @return true if this State is greater or equal to the given {@code state}
+         */
+        public boolean isAtLeast(State state) {
+            return compareTo(state) >= 0;
+        }
+    }
+}
diff --git a/lifecycle/common/src/main/java/android/arch/lifecycle/LifecycleObserver.java b/lifecycle/common/src/main/java/android/arch/lifecycle/LifecycleObserver.java
new file mode 100644
index 0000000..94670bf
--- /dev/null
+++ b/lifecycle/common/src/main/java/android/arch/lifecycle/LifecycleObserver.java
@@ -0,0 +1,28 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.arch.lifecycle;
+
+/**
+ * Marks a class as a LifecycleObserver. It does not have any methods, instead, relies on
+ * {@link OnLifecycleEvent} annotated methods.
+ * <p>
+ * @see Lifecycle Lifecycle - for samples and usage patterns.
+ */
+@SuppressWarnings("WeakerAccess")
+public interface LifecycleObserver {
+
+}
diff --git a/lifecycle/common/src/main/java/android/arch/lifecycle/LifecycleOwner.java b/lifecycle/common/src/main/java/android/arch/lifecycle/LifecycleOwner.java
new file mode 100644
index 0000000..934cf3a
--- /dev/null
+++ b/lifecycle/common/src/main/java/android/arch/lifecycle/LifecycleOwner.java
@@ -0,0 +1,33 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.arch.lifecycle;
+
+/**
+ * A class that has an Android lifecycle. These events can be used by custom components to
+ * handle lifecycle changes without implementing any code inside the Activity or the Fragment.
+ *
+ * @see Lifecycle
+ */
+@SuppressWarnings({"WeakerAccess", "unused"})
+public interface LifecycleOwner {
+    /**
+     * Returns the Lifecycle of the provider.
+     *
+     * @return The lifecycle of the provider.
+     */
+    Lifecycle getLifecycle();
+}
diff --git a/lifecycle/common/src/main/java/android/arch/lifecycle/Lifecycling.java b/lifecycle/common/src/main/java/android/arch/lifecycle/Lifecycling.java
new file mode 100644
index 0000000..295f731
--- /dev/null
+++ b/lifecycle/common/src/main/java/android/arch/lifecycle/Lifecycling.java
@@ -0,0 +1,113 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.arch.lifecycle;
+
+import android.support.annotation.NonNull;
+import android.support.annotation.Nullable;
+import android.support.annotation.RestrictTo;
+
+import java.lang.reflect.Constructor;
+import java.lang.reflect.InvocationTargetException;
+import java.util.HashMap;
+import java.util.Map;
+
+/**
+ * Internal class to handle lifecycle conversion etc.
+ * @hide
+ */
+@RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
+class Lifecycling {
+    private static Constructor<? extends GenericLifecycleObserver> sREFLECTIVE;
+
+    static {
+        try {
+            sREFLECTIVE = ReflectiveGenericLifecycleObserver.class
+                    .getDeclaredConstructor(Object.class);
+        } catch (NoSuchMethodException ignored) {
+
+        }
+    }
+
+    private static Map<Class, Constructor<? extends GenericLifecycleObserver>> sCallbackCache =
+            new HashMap<>();
+
+    @NonNull
+    static GenericLifecycleObserver getCallback(Object object) {
+        if (object instanceof GenericLifecycleObserver) {
+            return (GenericLifecycleObserver) object;
+        }
+        //noinspection TryWithIdenticalCatches
+        try {
+            final Class<?> klass = object.getClass();
+            Constructor<? extends GenericLifecycleObserver> cachedConstructor = sCallbackCache.get(
+                    klass);
+            if (cachedConstructor != null) {
+                return cachedConstructor.newInstance(object);
+            }
+            cachedConstructor = getGeneratedAdapterConstructor(klass);
+            if (cachedConstructor != null) {
+                if (!cachedConstructor.isAccessible()) {
+                    cachedConstructor.setAccessible(true);
+                }
+            } else {
+                cachedConstructor = sREFLECTIVE;
+            }
+            sCallbackCache.put(klass, cachedConstructor);
+            return cachedConstructor.newInstance(object);
+        } catch (IllegalAccessException e) {
+            throw new RuntimeException(e);
+        } catch (InstantiationException e) {
+            throw new RuntimeException(e);
+        } catch (InvocationTargetException e) {
+            throw new RuntimeException(e);
+        }
+    }
+
+    @Nullable
+    private static Constructor<? extends GenericLifecycleObserver> getGeneratedAdapterConstructor(
+            Class<?> klass) {
+        final String fullPackage = klass.getPackage().getName();
+
+        String name = klass.getCanonicalName();
+        // anonymous class bug:35073837
+        if (name == null) {
+            return null;
+        }
+        final String adapterName = getAdapterName(fullPackage.isEmpty() ? name :
+                name.substring(fullPackage.length() + 1));
+        try {
+            @SuppressWarnings("unchecked")
+            final Class<? extends GenericLifecycleObserver> aClass =
+                    (Class<? extends GenericLifecycleObserver>) Class.forName(
+                            fullPackage.isEmpty() ? adapterName : fullPackage + "." + adapterName);
+            return aClass.getDeclaredConstructor(klass);
+        } catch (ClassNotFoundException e) {
+            final Class<?> superclass = klass.getSuperclass();
+            if (superclass != null) {
+                return getGeneratedAdapterConstructor(superclass);
+            }
+        } catch (NoSuchMethodException e) {
+            // this should not happen
+            throw new RuntimeException(e);
+        }
+        return null;
+    }
+
+    static String getAdapterName(String className) {
+        return className.replace(".", "_") + "_LifecycleAdapter";
+    }
+}
diff --git a/lifecycle/common/src/main/java/android/arch/lifecycle/OnLifecycleEvent.java b/lifecycle/common/src/main/java/android/arch/lifecycle/OnLifecycleEvent.java
new file mode 100644
index 0000000..9a86b0c
--- /dev/null
+++ b/lifecycle/common/src/main/java/android/arch/lifecycle/OnLifecycleEvent.java
@@ -0,0 +1,29 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.arch.lifecycle;
+
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+@SuppressWarnings("unused")
+@Retention(RetentionPolicy.RUNTIME)
+@Target(ElementType.METHOD)
+public @interface OnLifecycleEvent {
+    Lifecycle.Event value();
+}
diff --git a/lifecycle/common/src/main/java/android/arch/lifecycle/ReflectiveGenericLifecycleObserver.java b/lifecycle/common/src/main/java/android/arch/lifecycle/ReflectiveGenericLifecycleObserver.java
new file mode 100644
index 0000000..7de32db
--- /dev/null
+++ b/lifecycle/common/src/main/java/android/arch/lifecycle/ReflectiveGenericLifecycleObserver.java
@@ -0,0 +1,224 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.arch.lifecycle;
+
+import android.arch.lifecycle.Lifecycle.Event;
+
+import java.lang.reflect.InvocationTargetException;
+import java.lang.reflect.Method;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Map.Entry;
+
+/**
+ * An internal implementation of {@link GenericLifecycleObserver} that relies on reflection.
+ */
+class ReflectiveGenericLifecycleObserver implements GenericLifecycleObserver {
+    private final Object mWrapped;
+    private final CallbackInfo mInfo;
+    @SuppressWarnings("WeakerAccess")
+    static final Map<Class, CallbackInfo> sInfoCache = new HashMap<>();
+
+    ReflectiveGenericLifecycleObserver(Object wrapped) {
+        mWrapped = wrapped;
+        mInfo = getInfo(mWrapped.getClass());
+    }
+
+    @Override
+    public void onStateChanged(LifecycleOwner source, Event event) {
+        invokeCallbacks(mInfo, source, event);
+    }
+
+    private void invokeMethodsForEvent(List<MethodReference> handlers, LifecycleOwner source,
+            Event event) {
+        if (handlers != null) {
+            for (int i = handlers.size() - 1; i >= 0; i--) {
+                MethodReference reference = handlers.get(i);
+                invokeCallback(reference, source, event);
+            }
+        }
+    }
+
+    @SuppressWarnings("ConstantConditions")
+    private void invokeCallbacks(CallbackInfo info, LifecycleOwner source, Event event) {
+        invokeMethodsForEvent(info.mEventToHandlers.get(event), source, event);
+        invokeMethodsForEvent(info.mEventToHandlers.get(Event.ON_ANY), source, event);
+    }
+
+    private void invokeCallback(MethodReference reference, LifecycleOwner source, Event event) {
+        //noinspection TryWithIdenticalCatches
+        try {
+            switch (reference.mCallType) {
+                case CALL_TYPE_NO_ARG:
+                    reference.mMethod.invoke(mWrapped);
+                    break;
+                case CALL_TYPE_PROVIDER:
+                    reference.mMethod.invoke(mWrapped, source);
+                    break;
+                case CALL_TYPE_PROVIDER_WITH_EVENT:
+                    reference.mMethod.invoke(mWrapped, source, event);
+                    break;
+            }
+        } catch (InvocationTargetException e) {
+            throw new RuntimeException("Failed to call observer method", e.getCause());
+        } catch (IllegalAccessException e) {
+            throw new RuntimeException(e);
+        }
+    }
+
+    @Override
+    public Object getReceiver() {
+        return mWrapped;
+    }
+
+    private static CallbackInfo getInfo(Class klass) {
+        CallbackInfo existing = sInfoCache.get(klass);
+        if (existing != null) {
+            return existing;
+        }
+        existing = createInfo(klass);
+        return existing;
+    }
+
+    private static void verifyAndPutHandler(Map<MethodReference, Event> handlers,
+            MethodReference newHandler, Event newEvent, Class klass) {
+        Event event = handlers.get(newHandler);
+        if (event != null && newEvent != event) {
+            Method method = newHandler.mMethod;
+            throw new IllegalArgumentException(
+                    "Method " + method.getName() + " in " + klass.getName()
+                            + " already declared with different @OnLifecycleEvent value: previous"
+                            + " value " + event + ", new value " + newEvent);
+        }
+        if (event == null) {
+            handlers.put(newHandler, newEvent);
+        }
+    }
+
+    private static CallbackInfo createInfo(Class klass) {
+        Class superclass = klass.getSuperclass();
+        Map<MethodReference, Event> handlerToEvent = new HashMap<>();
+        if (superclass != null) {
+            CallbackInfo superInfo = getInfo(superclass);
+            if (superInfo != null) {
+                handlerToEvent.putAll(superInfo.mHandlerToEvent);
+            }
+        }
+
+        Method[] methods = klass.getDeclaredMethods();
+
+        Class[] interfaces = klass.getInterfaces();
+        for (Class intrfc : interfaces) {
+            for (Entry<MethodReference, Event> entry : getInfo(intrfc).mHandlerToEvent.entrySet()) {
+                verifyAndPutHandler(handlerToEvent, entry.getKey(), entry.getValue(), klass);
+            }
+        }
+
+        for (Method method : methods) {
+            OnLifecycleEvent annotation = method.getAnnotation(OnLifecycleEvent.class);
+            if (annotation == null) {
+                continue;
+            }
+            Class<?>[] params = method.getParameterTypes();
+            int callType = CALL_TYPE_NO_ARG;
+            if (params.length > 0) {
+                callType = CALL_TYPE_PROVIDER;
+                if (!params[0].isAssignableFrom(LifecycleOwner.class)) {
+                    throw new IllegalArgumentException(
+                            "invalid parameter type. Must be one and instanceof LifecycleOwner");
+                }
+            }
+            Event event = annotation.value();
+
+            if (params.length > 1) {
+                callType = CALL_TYPE_PROVIDER_WITH_EVENT;
+                if (!params[1].isAssignableFrom(Event.class)) {
+                    throw new IllegalArgumentException(
+                            "invalid parameter type. second arg must be an event");
+                }
+                if (event != Event.ON_ANY) {
+                    throw new IllegalArgumentException(
+                            "Second arg is supported only for ON_ANY value");
+                }
+            }
+            if (params.length > 2) {
+                throw new IllegalArgumentException("cannot have more than 2 params");
+            }
+            MethodReference methodReference = new MethodReference(callType, method);
+            verifyAndPutHandler(handlerToEvent, methodReference, event, klass);
+        }
+        CallbackInfo info = new CallbackInfo(handlerToEvent);
+        sInfoCache.put(klass, info);
+        return info;
+    }
+
+    @SuppressWarnings("WeakerAccess")
+    static class CallbackInfo {
+        final Map<Event, List<MethodReference>> mEventToHandlers;
+        final Map<MethodReference, Event> mHandlerToEvent;
+
+        CallbackInfo(Map<MethodReference, Event> handlerToEvent) {
+            mHandlerToEvent = handlerToEvent;
+            mEventToHandlers = new HashMap<>();
+            for (Entry<MethodReference, Event> entry : handlerToEvent.entrySet()) {
+                Event event = entry.getValue();
+                List<MethodReference> methodReferences = mEventToHandlers.get(event);
+                if (methodReferences == null) {
+                    methodReferences = new ArrayList<>();
+                    mEventToHandlers.put(event, methodReferences);
+                }
+                methodReferences.add(entry.getKey());
+            }
+        }
+    }
+
+    @SuppressWarnings("WeakerAccess")
+    static class MethodReference {
+        final int mCallType;
+        final Method mMethod;
+
+        MethodReference(int callType, Method method) {
+            mCallType = callType;
+            mMethod = method;
+            mMethod.setAccessible(true);
+        }
+
+        @Override
+        public boolean equals(Object o) {
+            if (this == o) {
+                return true;
+            }
+            if (o == null || getClass() != o.getClass()) {
+                return false;
+            }
+
+            MethodReference that = (MethodReference) o;
+            return mCallType == that.mCallType && mMethod.getName().equals(that.mMethod.getName());
+        }
+
+        @Override
+        public int hashCode() {
+            return 31 * mCallType + mMethod.getName().hashCode();
+        }
+    }
+
+    private static final int CALL_TYPE_NO_ARG = 0;
+    private static final int CALL_TYPE_PROVIDER = 1;
+    private static final int CALL_TYPE_PROVIDER_WITH_EVENT = 2;
+}
diff --git a/lifecycle/common/src/test/java/android/arch/lifecycle/ReflectiveGenericLifecycleObserverTest.java b/lifecycle/common/src/test/java/android/arch/lifecycle/ReflectiveGenericLifecycleObserverTest.java
new file mode 100644
index 0000000..68f04e8
--- /dev/null
+++ b/lifecycle/common/src/test/java/android/arch/lifecycle/ReflectiveGenericLifecycleObserverTest.java
@@ -0,0 +1,365 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.arch.lifecycle;
+
+import static android.arch.lifecycle.Lifecycle.Event.ON_ANY;
+import static android.arch.lifecycle.Lifecycle.Event.ON_CREATE;
+import static android.arch.lifecycle.Lifecycle.Event.ON_DESTROY;
+import static android.arch.lifecycle.Lifecycle.Event.ON_PAUSE;
+import static android.arch.lifecycle.Lifecycle.Event.ON_RESUME;
+import static android.arch.lifecycle.Lifecycle.Event.ON_START;
+import static android.arch.lifecycle.Lifecycle.Event.ON_STOP;
+import static android.arch.lifecycle.Lifecycle.State.CREATED;
+import static android.arch.lifecycle.Lifecycle.State.INITIALIZED;
+import static android.arch.lifecycle.Lifecycle.State.RESUMED;
+import static android.arch.lifecycle.Lifecycle.State.STARTED;
+
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.hamcrest.core.Is.is;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.reset;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+import org.mockito.Matchers;
+
+@RunWith(JUnit4.class)
+public class ReflectiveGenericLifecycleObserverTest {
+    private LifecycleOwner mOwner;
+    private Lifecycle mLifecycle;
+
+    @Before
+    public void initMocks() {
+        mOwner = mock(LifecycleOwner.class);
+        mLifecycle = mock(Lifecycle.class);
+        when(mOwner.getLifecycle()).thenReturn(mLifecycle);
+    }
+
+    @Test
+    public void anyState() {
+        AnyStateListener obj = mock(AnyStateListener.class);
+        ReflectiveGenericLifecycleObserver observer = new ReflectiveGenericLifecycleObserver(obj);
+        when(mLifecycle.getCurrentState()).thenReturn(STARTED);
+        observer.onStateChanged(mOwner, ON_CREATE);
+        verify(obj).onAnyState(mOwner, ON_CREATE);
+        reset(obj);
+
+        observer.onStateChanged(mOwner, ON_START);
+        verify(obj).onAnyState(mOwner, ON_START);
+        reset(obj);
+
+        observer.onStateChanged(mOwner, ON_RESUME);
+        verify(obj).onAnyState(mOwner, ON_RESUME);
+        reset(obj);
+
+        observer.onStateChanged(mOwner, ON_PAUSE);
+        verify(obj).onAnyState(mOwner, ON_PAUSE);
+        reset(obj);
+
+        observer.onStateChanged(mOwner, ON_STOP);
+        verify(obj).onAnyState(mOwner, ON_STOP);
+        reset(obj);
+
+        observer.onStateChanged(mOwner, ON_DESTROY);
+        verify(obj).onAnyState(mOwner, ON_DESTROY);
+        reset(obj);
+    }
+
+    private static class AnyStateListener implements LifecycleObserver {
+        @OnLifecycleEvent(ON_ANY)
+        void onAnyState(LifecycleOwner owner, Lifecycle.Event event) {
+
+        }
+    }
+
+    @Test
+    public void singleMethod() {
+        CreatedStateListener obj = mock(CreatedStateListener.class);
+        ReflectiveGenericLifecycleObserver observer = new ReflectiveGenericLifecycleObserver(obj);
+        when(mLifecycle.getCurrentState()).thenReturn(CREATED);
+        observer.onStateChanged(mOwner, ON_CREATE);
+        verify(obj).onCreated();
+        verify(obj).onCreated(mOwner);
+    }
+
+    private static class CreatedStateListener implements LifecycleObserver {
+        @OnLifecycleEvent(ON_CREATE)
+        void onCreated() {
+
+        }
+        @SuppressWarnings("UnusedParameters")
+        @OnLifecycleEvent(ON_CREATE)
+        void onCreated(LifecycleOwner provider) {
+
+        }
+    }
+
+    @Test
+    public void eachEvent() {
+        AllMethodsListener obj = mock(AllMethodsListener.class);
+        ReflectiveGenericLifecycleObserver observer = new ReflectiveGenericLifecycleObserver(obj);
+        when(mLifecycle.getCurrentState()).thenReturn(CREATED);
+
+        observer.onStateChanged(mOwner, ON_CREATE);
+        verify(obj).created();
+        reset(obj);
+
+        when(mLifecycle.getCurrentState()).thenReturn(STARTED);
+        observer.onStateChanged(mOwner, ON_START);
+        verify(obj).started();
+        reset(obj);
+
+        when(mLifecycle.getCurrentState()).thenReturn(RESUMED);
+        observer.onStateChanged(mOwner, ON_RESUME);
+        verify(obj).resumed();
+        reset(obj);
+
+        when(mLifecycle.getCurrentState()).thenReturn(STARTED);
+        observer.onStateChanged(mOwner, ON_PAUSE);
+        verify(obj).paused();
+        reset(obj);
+
+        when(mLifecycle.getCurrentState()).thenReturn(CREATED);
+        observer.onStateChanged(mOwner, ON_STOP);
+        verify(obj).stopped();
+        reset(obj);
+
+        when(mLifecycle.getCurrentState()).thenReturn(INITIALIZED);
+        observer.onStateChanged(mOwner, ON_DESTROY);
+        verify(obj).destroyed();
+        reset(obj);
+    }
+
+
+    private static class AllMethodsListener implements LifecycleObserver {
+        @OnLifecycleEvent(ON_CREATE)
+        void created() {}
+
+        @OnLifecycleEvent(ON_START)
+        void started() {}
+
+        @OnLifecycleEvent(ON_RESUME)
+        void resumed() {}
+
+        @OnLifecycleEvent(ON_PAUSE)
+        void paused() {}
+
+        @OnLifecycleEvent(ON_STOP)
+        void stopped() {}
+
+        @OnLifecycleEvent(ON_DESTROY)
+        void destroyed() {
+        }
+    }
+
+    @Test
+    public void testFailingObserver() {
+        class UnprecedentedError extends Error {
+        }
+
+        LifecycleObserver obj = new LifecycleObserver() {
+            @OnLifecycleEvent(ON_START)
+            void started() {
+                throw new UnprecedentedError();
+            }
+        };
+        ReflectiveGenericLifecycleObserver observer = new ReflectiveGenericLifecycleObserver(obj);
+        try {
+            observer.onStateChanged(mOwner, ON_START);
+        } catch (Exception e) {
+            assertThat("exception cause is wrong",
+                    e.getCause() instanceof UnprecedentedError);
+        }
+    }
+
+    @Test
+    public void testPrivateObserverMethods() {
+        class ObserverWithPrivateMethod implements LifecycleObserver {
+            boolean mCalled = false;
+            @OnLifecycleEvent(ON_START)
+            private void started() {
+                mCalled = true;
+            }
+        }
+
+        ObserverWithPrivateMethod obj = mock(ObserverWithPrivateMethod.class);
+        ReflectiveGenericLifecycleObserver observer = new ReflectiveGenericLifecycleObserver(obj);
+        observer.onStateChanged(mOwner, ON_START);
+        assertThat(obj.mCalled, is(true));
+    }
+
+    @Test(expected = IllegalArgumentException.class)
+    public void testWrongFirstParam1() {
+        LifecycleObserver observer = new LifecycleObserver() {
+            @OnLifecycleEvent(ON_START)
+            private void started(Lifecycle.Event e) {
+            }
+        };
+        new ReflectiveGenericLifecycleObserver(observer);
+    }
+
+    @Test(expected = IllegalArgumentException.class)
+    public void testWrongFirstParam2() {
+        LifecycleObserver observer = new LifecycleObserver() {
+            @OnLifecycleEvent(ON_ANY)
+            private void started(Lifecycle l, Lifecycle.Event e) {
+            }
+        };
+        new ReflectiveGenericLifecycleObserver(observer);
+    }
+
+    @Test(expected = IllegalArgumentException.class)
+    public void testWrongSecondParam() {
+        LifecycleObserver observer = new LifecycleObserver() {
+            @OnLifecycleEvent(ON_START)
+            private void started(LifecycleOwner owner, Lifecycle l) {
+            }
+        };
+        new ReflectiveGenericLifecycleObserver(observer);
+    }
+
+    @Test(expected = IllegalArgumentException.class)
+    public void testThreeParams() {
+        LifecycleObserver observer = new LifecycleObserver() {
+            @OnLifecycleEvent(ON_ANY)
+            private void started(LifecycleOwner owner, Lifecycle.Event e, int i) {
+            }
+        };
+        new ReflectiveGenericLifecycleObserver(observer);
+    }
+
+    class BaseClass1 implements LifecycleObserver {
+        @OnLifecycleEvent(ON_START)
+        void foo(LifecycleOwner owner) {
+        }
+    }
+
+    class DerivedClass1 extends BaseClass1 {
+        @OnLifecycleEvent(ON_STOP)
+        void foo(LifecycleOwner owner) {
+        }
+    }
+
+    @Test(expected = IllegalArgumentException.class)
+    public void testInvalidSuper1() {
+        new ReflectiveGenericLifecycleObserver(new DerivedClass1());
+    }
+
+    class BaseClass2 implements LifecycleObserver {
+        @OnLifecycleEvent(ON_START)
+        void foo(LifecycleOwner owner) {
+        }
+    }
+
+    class DerivedClass2 extends BaseClass1 {
+        @OnLifecycleEvent(ON_STOP)
+        void foo() {
+        }
+    }
+
+    @Test
+    public void testValidSuper1() {
+        DerivedClass2 obj = mock(DerivedClass2.class);
+        ReflectiveGenericLifecycleObserver observer = new ReflectiveGenericLifecycleObserver(obj);
+        observer.onStateChanged(mock(LifecycleOwner.class), ON_START);
+        verify(obj).foo(Matchers.<LifecycleOwner>any());
+        verify(obj, never()).foo();
+        reset(obj);
+        observer.onStateChanged(mock(LifecycleOwner.class), ON_STOP);
+        verify(obj).foo();
+        verify(obj, never()).foo(Matchers.<LifecycleOwner>any());
+    }
+
+    class BaseClass3 implements LifecycleObserver {
+        @OnLifecycleEvent(ON_START)
+        void foo(LifecycleOwner owner) {
+        }
+    }
+
+    interface Interface3 extends LifecycleObserver {
+        @OnLifecycleEvent(ON_STOP)
+        void foo(LifecycleOwner owner);
+    }
+
+    class DerivedClass3 extends BaseClass3 implements Interface3 {
+        @Override
+        public void foo(LifecycleOwner owner) {
+        }
+    }
+
+    @Test(expected = IllegalArgumentException.class)
+    public void testInvalidSuper2() {
+        new ReflectiveGenericLifecycleObserver(new DerivedClass3());
+    }
+
+    class BaseClass4 implements LifecycleObserver {
+        @OnLifecycleEvent(ON_START)
+        void foo(LifecycleOwner owner) {
+        }
+    }
+
+    interface Interface4 extends LifecycleObserver {
+        @OnLifecycleEvent(ON_START)
+        void foo(LifecycleOwner owner);
+    }
+
+    class DerivedClass4 extends BaseClass4 implements Interface4 {
+        @Override
+        @OnLifecycleEvent(ON_START)
+        public void foo(LifecycleOwner owner) {
+        }
+
+        @OnLifecycleEvent(ON_START)
+        public void foo() {
+        }
+    }
+
+    @Test
+    public void testValidSuper2() {
+        DerivedClass4 obj = mock(DerivedClass4.class);
+        ReflectiveGenericLifecycleObserver observer = new ReflectiveGenericLifecycleObserver(obj);
+        observer.onStateChanged(mock(LifecycleOwner.class), ON_START);
+        verify(obj).foo(Matchers.<LifecycleOwner>any());
+        verify(obj).foo();
+    }
+
+    interface InterfaceStart extends LifecycleObserver {
+        @OnLifecycleEvent(ON_START)
+        void foo(LifecycleOwner owner);
+    }
+
+    interface InterfaceStop extends LifecycleObserver {
+        @OnLifecycleEvent(ON_STOP)
+        void foo(LifecycleOwner owner);
+    }
+
+    class DerivedClass5 implements InterfaceStart, InterfaceStop {
+        @Override
+        public void foo(LifecycleOwner owner) {
+        }
+    }
+
+    @Test(expected = IllegalArgumentException.class)
+    public void testInvalidSuper3() {
+        new ReflectiveGenericLifecycleObserver(new DerivedClass5());
+    }
+}
diff --git a/lifecycle/compiler/build.gradle b/lifecycle/compiler/build.gradle
new file mode 100644
index 0000000..628204e
--- /dev/null
+++ b/lifecycle/compiler/build.gradle
@@ -0,0 +1,20 @@
+apply plugin: 'kotlin'
+apply plugin: 'maven'
+apply plugin: 'checkstyle'
+
+sourceSets {
+    test.java.srcDirs += 'src/tests/kotlin'
+}
+
+dependencies {
+    compile project(":lifecycle:common")
+    compile libs.kotlin.stdlib
+    compile libs.auto_common
+    compile libs.javapoet
+    testCompile libs.google_compile_testing
+    testCompile files(org.gradle.internal.jvm.Jvm.current().getToolsJar())
+}
+
+archivesBaseName = "compiler"
+
+createKotlinCheckstyle(project)
diff --git a/lifecycle/compiler/reset_results.py b/lifecycle/compiler/reset_results.py
new file mode 100644
index 0000000..994f20d
--- /dev/null
+++ b/lifecycle/compiler/reset_results.py
@@ -0,0 +1,62 @@
+#!/usr/bin/python
+
+#
+# Copyright 2017, The Android Open Source Project
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#     http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+
+""" Run this script when you need to update test-data/expected."""
+import sys
+
+if len(sys.argv) != 2:
+    print("You need to specify the only one param: file with test failures")
+    sys.exit()
+
+with open(sys.argv[1]) as f:
+    content = f.readlines()
+
+with open("src/tests/test-data/expected/license.txt") as license:
+    licenseLines = license.readlines()
+
+
+def writeToFile(fileName, lines):
+    file = open("src/tests/test-data/expected/" + fileName, "w")
+    for line in lines:
+        file.write(line)
+
+    file.close()
+
+
+state = 0
+filename = ""
+expected = "Expected file:"
+fileLines = []
+for line in content:
+    if (state == 0 and line.startswith(expected)):
+        state = 1
+        filename  = line[line.rfind("/") + 1 : len(line) - 2]
+        print(filename)
+
+    if state == 1 and line.startswith("Actual Source:"):
+        state = 2
+        continue
+
+    if state == 2:
+        fileLines.append(line)
+
+    if state == 2 and line.rstrip() == "}":
+        writeToFile(filename, licenseLines + fileLines[1:])
+        state = 0
+        fileLines = []
+
diff --git a/lifecycle/compiler/src/main/kotlin/android/arch/lifecycle/LifecycleProcessor.kt b/lifecycle/compiler/src/main/kotlin/android/arch/lifecycle/LifecycleProcessor.kt
new file mode 100644
index 0000000..30aaa3e
--- /dev/null
+++ b/lifecycle/compiler/src/main/kotlin/android/arch/lifecycle/LifecycleProcessor.kt
@@ -0,0 +1,375 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.arch.lifecycle
+
+import com.google.auto.common.MoreElements
+import com.google.auto.common.MoreTypes
+import com.squareup.javapoet.ClassName
+import com.squareup.javapoet.FieldSpec
+import com.squareup.javapoet.JavaFile
+import com.squareup.javapoet.MethodSpec
+import com.squareup.javapoet.ParameterSpec
+import com.squareup.javapoet.TypeName
+import com.squareup.javapoet.TypeSpec
+import java.util.LinkedList
+import javax.annotation.processing.*
+import javax.lang.model.SourceVersion
+import javax.lang.model.element.*
+import javax.lang.model.element.Modifier.PRIVATE
+import javax.lang.model.element.Modifier.PROTECTED
+import javax.lang.model.element.Modifier.PUBLIC
+import javax.lang.model.type.NoType
+import javax.lang.model.type.TypeMirror
+import javax.tools.Diagnostic
+
+fun Element.getPackage(): PackageElement = MoreElements.getPackage(this)
+fun Element.getPackageQName() = getPackage().qualifiedName.toString()
+fun ExecutableElement.name() = simpleName.toString()
+fun ExecutableElement.isPackagePrivate() = !modifiers.any {
+    it == PUBLIC || it == PROTECTED || it == PRIVATE
+}
+
+fun ExecutableElement.isProtected() = modifiers.contains(PROTECTED)
+
+@SupportedAnnotationTypes("android.arch.lifecycle.OnLifecycleEvent")
+@SupportedSourceVersion(SourceVersion.RELEASE_7)
+class LifecycleProcessor : AbstractProcessor() {
+    companion object ErrorMessages {
+        const val TOO_MANY_ARGS = "callback method cannot have more than 2 parameters"
+        const val TOO_MANY_ARGS_NOT_ON_ANY = "only callback annotated with ON_ANY " +
+                "can have 2 parameters"
+        const val INVALID_SECOND_ARGUMENT = "2nd argument of a callback method" +
+                " must be Lifecycle.Event and represent the current event"
+        const val INVALID_FIRST_ARGUMENT = "1st argument of a callback method must be " +
+                "a LifecycleOwner which represents the source of the event"
+        const val INVALID_METHOD_MODIFIER = "method marked with OnLifecycleEvent annotation can " +
+                "not be private"
+        const val INVALID_CLASS_MODIFIER = "class containing OnLifecycleEvent methods can not be " +
+                "private"
+        const val INVALID_STATE_OVERRIDE_METHOD = "overridden method must handle the same " +
+                "onState changes as original method"
+    }
+
+    private val LIFECYCLE_OWNER = ClassName.get(LifecycleOwner::class.java)
+    private val JAVA_LIFECYCLE_EVENT = Lifecycle.Event::class.java
+    private val T = "\$T"
+    private val N = "\$N"
+    private val L = "\$L"
+
+    private fun printErrorMessage(msg: CharSequence, elem: Element) {
+        processingEnv.messager.printMessage(Diagnostic.Kind.ERROR, msg, elem)
+    }
+
+    private fun validateParam(param: VariableElement,
+                              expectedType: Class<*>, errorMsg: String): Boolean {
+        if (!MoreTypes.isTypeOf(expectedType, param.asType())) {
+            printErrorMessage(errorMsg, param)
+            return false
+        }
+        return true
+    }
+
+    private fun validateMethod(method: ExecutableElement, event: Lifecycle.Event): Boolean {
+        if (PRIVATE in method.modifiers) {
+            printErrorMessage(INVALID_METHOD_MODIFIER, method)
+            return false
+        }
+        val params = method.parameters
+        if ((params.size > 2)) {
+            printErrorMessage(TOO_MANY_ARGS, method)
+            return false
+        }
+
+        if (params.size == 2 && event != Lifecycle.Event.ON_ANY) {
+            printErrorMessage(TOO_MANY_ARGS_NOT_ON_ANY, method)
+            return false
+        }
+
+        if (params.size == 2 && !validateParam(params[1], JAVA_LIFECYCLE_EVENT,
+                INVALID_SECOND_ARGUMENT)) {
+            return false
+        }
+
+        if (params.size > 0) {
+            return validateParam(params[0], LifecycleOwner::class.java,
+                    INVALID_FIRST_ARGUMENT)
+        }
+        return true
+    }
+
+    private fun validateClass(classElement: Element): Boolean {
+        if (classElement.kind != ElementKind.CLASS && classElement.kind != ElementKind.INTERFACE) {
+            printErrorMessage("Parent of OnLifecycleEvent should be a class or interface",
+                    classElement)
+            return false
+        }
+        if (PRIVATE in classElement.modifiers) {
+            printErrorMessage(INVALID_CLASS_MODIFIER, classElement)
+            return false
+        }
+        return true
+    }
+
+    override fun process(annotations: MutableSet<out TypeElement>,
+                         roundEnv: RoundEnvironment): Boolean {
+        val world = roundEnv.getElementsAnnotatedWith(OnLifecycleEvent::class.java).map { elem ->
+            if (elem.kind != ElementKind.METHOD) {
+                printErrorMessage("OnLifecycleEvent can only be added to methods", elem)
+                null
+            } else {
+                val enclosingElement = elem.enclosingElement
+                val onState = elem.getAnnotation(OnLifecycleEvent::class.java)
+                val method = MoreElements.asExecutable(elem)
+                if (validateClass(enclosingElement) && validateMethod(method, onState.value)) {
+                    StateMethod(method, onState)
+                } else {
+                    null
+                }
+            }
+        }
+                .filterNotNull()
+                .groupBy { MoreElements.asType(it.method.enclosingElement) }
+                .mapValues { entry -> LifecycleObserverInfo(entry.key, entry.value) }
+
+
+        flattenObserverInfos(world).forEach {
+            writeAdapter(it)
+        }
+
+        return true
+    }
+
+    private fun superObservers(world: Map<TypeElement, LifecycleObserverInfo>,
+                               observer: LifecycleObserverInfo): List<LifecycleObserverInfo> {
+        val stack = LinkedList<TypeMirror>()
+        stack += observer.type.interfaces.reversed()
+        stack += observer.type.superclass
+        val result = mutableListOf<LifecycleObserverInfo>()
+        while (stack.isNotEmpty()) {
+            val typeMirror = stack.removeLast()
+            if (typeMirror is NoType) {
+                continue
+            }
+            val type = MoreTypes.asTypeElement(typeMirror)
+            val currentObserver = world[type]
+            if (currentObserver != null) {
+                result.add(currentObserver)
+            } else {
+                stack += type.interfaces.reversed()
+                stack += type.superclass
+            }
+        }
+        return result
+    }
+
+    private fun mergeAndVerifyMethods(classMethods: List<StateMethod>,
+                                      parentMethods: List<StateMethod>): List<StateMethod> {
+        return parentMethods + classMethods.filter { currentMethod ->
+            val baseMethod = parentMethods.find { m ->
+                currentMethod.method.simpleName == m.method.simpleName
+                        && currentMethod.method.parameters.size == m.method.parameters.size
+            }
+            if (baseMethod != null
+                    && baseMethod.onLifecycleEvent != currentMethod.onLifecycleEvent) {
+                printErrorMessage(INVALID_STATE_OVERRIDE_METHOD, currentMethod.method)
+            }
+            baseMethod == null
+        }
+
+    }
+
+    private fun flattenObserverInfos(
+            world: Map<TypeElement, LifecycleObserverInfo>): List<LifecycleObserverInfo> {
+        val superObservers = world.mapValues { superObservers(world, it.value) }
+        val packagePrivateMethods = world.mapValues { observer ->
+            if (observer.value.type.kind.isInterface) {
+                emptyList()
+            } else {
+                observer.value.methods.filter {
+                    it.method.isPackagePrivate() || it.method.isProtected()
+                }.map { it.method }
+            }
+        }
+
+        val ppMethodsToType = packagePrivateMethods.entries.fold(
+                mapOf<ExecutableElement, TypeElement>(), { map, entry ->
+            map + entry.value.associate { it to entry.key }
+        })
+
+        world.values.forEach {
+            val observers = superObservers[it.type]!!
+            val currentPackage = it.type.getPackageQName()
+            observers.filter { superObserver ->
+                superObserver.type.getPackageQName() != currentPackage
+                        && packagePrivateMethods[superObserver.type]!!.isNotEmpty()
+            }.forEach { it.syntheticMethods.addAll(packagePrivateMethods[it.type]!!) }
+        }
+
+
+        val flattened: MutableMap<LifecycleObserverInfo, LifecycleObserverInfo> = mutableMapOf()
+        fun traverse(observer: LifecycleObserverInfo) {
+            if (observer in flattened) {
+                return
+            }
+            val observers = superObservers[observer.type]!!
+            if (observers.isEmpty()) {
+                flattened[observer] = observer
+                return
+            }
+            observers.filter { it !in flattened }.forEach(::traverse)
+            val currentPackage = observer.type.getPackageQName()
+            val methods = observers.fold(emptyList<StateMethod>(),
+                    { list, observer -> mergeAndVerifyMethods(observer.methods, list) }).map {
+                val packageName = ppMethodsToType[it.method]?.getPackageQName()
+                if (packageName == null || packageName == currentPackage) {
+                    it
+                } else {
+                    StateMethod(it.method, it.onLifecycleEvent, ppMethodsToType[it.method])
+                }
+            }
+
+            flattened[observer] = LifecycleObserverInfo(observer.type,
+                    mergeAndVerifyMethods(observer.methods, methods), observer.syntheticMethods)
+        }
+
+        world.values.forEach(::traverse)
+        return flattened.values.toList()
+    }
+
+    private fun writeAdapter(observer: LifecycleObserverInfo) {
+        val ownerParam = ParameterSpec.builder(LIFECYCLE_OWNER, "owner").build()
+        val eventParam = ParameterSpec.builder(ClassName.get(JAVA_LIFECYCLE_EVENT), "event").build()
+        val receiverName = "mReceiver"
+        val receiverField = FieldSpec.builder(ClassName.get(observer.type), receiverName,
+                Modifier.FINAL).build()
+
+        val dispatchMethodBuilder = MethodSpec.methodBuilder("onStateChanged")
+                .returns(TypeName.VOID)
+                .addParameter(ownerParam)
+                .addParameter(eventParam)
+                .addModifiers(PUBLIC)
+                .addAnnotation(Override::class.java)
+        val dispatchMethod = dispatchMethodBuilder.apply {
+            observer.methods
+                    .groupBy { stateMethod -> stateMethod.onLifecycleEvent.value }
+                    .forEach { entry ->
+                        val event = entry.key
+                        val methods = entry.value
+                        if (event == Lifecycle.Event.ON_ANY) {
+                            writeMethodCalls(eventParam, methods, ownerParam, receiverField)
+                        } else {
+                            beginControlFlow("if ($N == $T.$L)", eventParam, JAVA_LIFECYCLE_EVENT, event)
+                                    .writeMethodCalls(eventParam, methods, ownerParam, receiverField)
+                            endControlFlow()
+                        }
+                    }
+        }.build()
+
+        @Suppress("PLATFORM_CLASS_MAPPED_TO_KOTLIN")
+        val getWrappedMethod = MethodSpec.methodBuilder("getReceiver")
+                .returns(ClassName.get(Object::class.java))
+                .addModifiers(PUBLIC)
+                .addStatement("return $N", receiverField)
+                .build()
+
+        val receiverParam = ParameterSpec.builder(ClassName.get(observer.type), "receiver").build()
+
+        val syntheticMethods = observer.syntheticMethods.map {
+            val method = MethodSpec.methodBuilder(syntheticName(it))
+                    .returns(TypeName.VOID)
+                    .addModifiers(PUBLIC)
+                    .addModifiers(Modifier.STATIC)
+                    .addParameter(receiverParam)
+            if (it.parameters.size >= 1) {
+                method.addParameter(ownerParam)
+            }
+            if (it.parameters.size == 2) {
+                method.addParameter(eventParam)
+            }
+
+            val count = it.parameters.size
+            val paramString = generateParamString(count)
+            method.addStatement("$N.$L($paramString)", receiverParam, it.name(),
+                    *takeParams(count, ownerParam, eventParam))
+            method.build()
+        }
+
+        val constructor = MethodSpec.constructorBuilder()
+                .addParameter(receiverParam)
+                .addStatement("this.$N = $N", receiverField, receiverParam)
+                .build()
+
+        val adapterName = getAdapterName(observer.type)
+        val adapter = TypeSpec.classBuilder(adapterName)
+                .addModifiers(PUBLIC)
+                .addSuperinterface(ClassName.get(GenericLifecycleObserver::class.java))
+                .addField(receiverField)
+                .addMethod(constructor)
+                .addMethod(dispatchMethod)
+                .addMethod(getWrappedMethod)
+                .addMethods(syntheticMethods)
+                .build()
+        JavaFile.builder(observer.type.getPackageQName(), adapter)
+                .build().writeTo(processingEnv.filer)
+    }
+
+    private fun MethodSpec.Builder.writeMethodCalls(eventParam: ParameterSpec,
+                                                    methods: List<StateMethod>,
+                                                    ownerParam: ParameterSpec,
+                                                    receiverField: FieldSpec) {
+        methods.forEach { method ->
+            val count = method.method.parameters.size
+            if (method.syntheticAccess == null) {
+                val paramString = generateParamString(count)
+                addStatement("$N.$L($paramString)", receiverField,
+                        method.method.name(),
+                        *takeParams(count, ownerParam, eventParam))
+
+            } else {
+                val originalType = method.syntheticAccess
+                val paramString = generateParamString(count + 1)
+                val className = ClassName.get(originalType.getPackageQName(),
+                        getAdapterName(originalType))
+                addStatement("$T.$L($paramString)", className,
+                        syntheticName(method.method),
+                        *takeParams(count + 1, receiverField, ownerParam,
+                                eventParam))
+            }
+        }
+    }
+
+    private fun syntheticName(method: ExecutableElement) = "__synthetic_" + method.simpleName
+
+    private fun takeParams(count: Int, vararg params: Any) = params.take(count).toTypedArray()
+
+    private fun generateParamString(count: Int) = (0..(count - 1)).joinToString(",") { N }
+
+    private fun getAdapterName(type: TypeElement): String {
+        val packageElement = type.getPackage()
+        val qName = type.qualifiedName.toString()
+        val partialName = if (packageElement.isUnnamed) qName else qName.substring(
+                packageElement.qualifiedName.toString().length + 1)
+        return Lifecycling.getAdapterName(partialName)
+    }
+
+    data class StateMethod(val method: ExecutableElement, val onLifecycleEvent: OnLifecycleEvent,
+                           val syntheticAccess: TypeElement? = null)
+
+    data class LifecycleObserverInfo(val type: TypeElement, val methods: List<StateMethod>,
+                                     var syntheticMethods:
+                                     MutableSet<ExecutableElement> = mutableSetOf())
+}
diff --git a/lifecycle/compiler/src/main/resources/META-INF/services/javax.annotation.processing.Processor b/lifecycle/compiler/src/main/resources/META-INF/services/javax.annotation.processing.Processor
new file mode 100644
index 0000000..cdabd71
--- /dev/null
+++ b/lifecycle/compiler/src/main/resources/META-INF/services/javax.annotation.processing.Processor
@@ -0,0 +1 @@
+android.arch.lifecycle.LifecycleProcessor
diff --git a/lifecycle/compiler/src/main/resources/NOTICE.txt b/lifecycle/compiler/src/main/resources/NOTICE.txt
new file mode 100644
index 0000000..28a4a72
--- /dev/null
+++ b/lifecycle/compiler/src/main/resources/NOTICE.txt
@@ -0,0 +1,1077 @@
+List of 3rd party licenses:
+-----------------------------------------------------------------------------
+* javapoet.jar (com.squareup:javapoet:1.8.0)
+
+ ****** 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
+
+   APPENDIX: How to apply the Apache License to your work.
+
+      To apply the Apache License to your work, attach the following
+      boilerplate notice, with the fields enclosed by brackets "[]"
+      replaced with your own identifying information. (Don't include
+      the brackets!)  The text should be enclosed in the appropriate
+      comment syntax for the file format. We also recommend that a
+      file or class name and description of purpose be included on the
+      same "printed page" as the copyright notice for easier
+      identification within third-party archives.
+
+   Copyright [yyyy] [name of copyright owner]
+
+   Licensed under the Apache License, Version 2.0 (the "License");
+   you may not use this file except in compliance with the License.
+   You may obtain a copy of the License at
+
+       http://www.apache.org/licenses/LICENSE-2.0
+
+   Unless required by applicable law or agreed to in writing, software
+   distributed under the License is distributed on an "AS IS" BASIS,
+   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+   See the License for the specific language governing permissions and
+   limitations under the License.
+
+
+
+
+-----------------------------------------------------------------------------
+* kotlin-stdlib.jar (org.jetbrains.kotlin:kotlin-stdlib:1.1.1)
+
+ ****** NOTICE:
+   =========================================================================
+   ==  NOTICE file corresponding to the section 4 d of                    ==
+   ==  the Apache License, Version 2.0,                                   ==
+   ==  in this case for the Kotlin Compiler distribution.                 ==
+   =========================================================================
+
+   Kotlin Compiler
+   Copyright 2010-2015 JetBrains s.r.o and respective authors and developers
+
+ ****** LICENSE:
+/*
+ * Copyright 2010-2017 JetBrains s.r.o.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+ ****** 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
+
+   APPENDIX: How to apply the Apache License to your work.
+
+      To apply the Apache License to your work, attach the following
+      boilerplate notice, with the fields enclosed by brackets "[]"
+      replaced with your own identifying information. (Don't include
+      the brackets!)  The text should be enclosed in the appropriate
+      comment syntax for the file format. We also recommend that a
+      file or class name and description of purpose be included on the
+      same "printed page" as the copyright notice for easier
+      identification within third-party archives.
+
+   Copyright [yyyy] [name of copyright owner]
+
+   Licensed under the Apache License, Version 2.0 (the "License");
+   you may not use this file except in compliance with the License.
+   You may obtain a copy of the License at
+
+       http://www.apache.org/licenses/LICENSE-2.0
+
+   Unless required by applicable law or agreed to in writing, software
+   distributed under the License is distributed on an "AS IS" BASIS,
+   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+   See the License for the specific language governing permissions and
+   limitations under the License.
+
+
+
+
+-----------------------------------------------------------------------------
+* auto-common.jar (com.google.auto:auto-common:0.6)
+
+ ****** 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
+
+   APPENDIX: How to apply the Apache License to your work.
+
+      To apply the Apache License to your work, attach the following
+      boilerplate notice, with the fields enclosed by brackets "[]"
+      replaced with your own identifying information. (Don't include
+      the brackets!)  The text should be enclosed in the appropriate
+      comment syntax for the file format. We also recommend that a
+      file or class name and description of purpose be included on the
+      same "printed page" as the copyright notice for easier
+      identification within third-party archives.
+
+   Copyright [yyyy] [name of copyright owner]
+
+   Licensed under the Apache License, Version 2.0 (the "License");
+   you may not use this file except in compliance with the License.
+   You may obtain a copy of the License at
+
+       http://www.apache.org/licenses/LICENSE-2.0
+
+   Unless required by applicable law or agreed to in writing, software
+   distributed under the License is distributed on an "AS IS" BASIS,
+   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+   See the License for the specific language governing permissions and
+   limitations under the License.
+
+
+
+
+-----------------------------------------------------------------------------
+* annotations.jar (org.jetbrains:annotations:13.0)
+
+ ****** 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
+
+   APPENDIX: How to apply the Apache License to your work.
+
+      To apply the Apache License to your work, attach the following
+      boilerplate notice, with the fields enclosed by brackets "[]"
+      replaced with your own identifying information. (Don't include
+      the brackets!)  The text should be enclosed in the appropriate
+      comment syntax for the file format. We also recommend that a
+      file or class name and description of purpose be included on the
+      same "printed page" as the copyright notice for easier
+      identification within third-party archives.
+
+   Copyright [yyyy] [name of copyright owner]
+
+   Licensed under the Apache License, Version 2.0 (the "License");
+   you may not use this file except in compliance with the License.
+   You may obtain a copy of the License at
+
+       http://www.apache.org/licenses/LICENSE-2.0
+
+   Unless required by applicable law or agreed to in writing, software
+   distributed under the License is distributed on an "AS IS" BASIS,
+   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+   See the License for the specific language governing permissions and
+   limitations under the License.
+
+
+
+
+-----------------------------------------------------------------------------
+* guava.jar (com.google.guava:guava:18.0)
+
+ ****** 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
+
+   APPENDIX: How to apply the Apache License to your work.
+
+      To apply the Apache License to your work, attach the following
+      boilerplate notice, with the fields enclosed by brackets "[]"
+      replaced with your own identifying information. (Don't include
+      the brackets!)  The text should be enclosed in the appropriate
+      comment syntax for the file format. We also recommend that a
+      file or class name and description of purpose be included on the
+      same "printed page" as the copyright notice for easier
+      identification within third-party archives.
+
+   Copyright [yyyy] [name of copyright owner]
+
+   Licensed under the Apache License, Version 2.0 (the "License");
+   you may not use this file except in compliance with the License.
+   You may obtain a copy of the License at
+
+       http://www.apache.org/licenses/LICENSE-2.0
+
+   Unless required by applicable law or agreed to in writing, software
+   distributed under the License is distributed on an "AS IS" BASIS,
+   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+   See the License for the specific language governing permissions and
+   limitations under the License.
+
+
+
diff --git a/lifecycle/compiler/src/tests/kotlin/android/arch/lifecycle/InvalidCasesTest.kt b/lifecycle/compiler/src/tests/kotlin/android/arch/lifecycle/InvalidCasesTest.kt
new file mode 100644
index 0000000..000e1bd
--- /dev/null
+++ b/lifecycle/compiler/src/tests/kotlin/android/arch/lifecycle/InvalidCasesTest.kt
@@ -0,0 +1,49 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.arch.lifecycle
+
+import android.arch.lifecycle.utils.processClass
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.junit.runners.Parameterized
+
+@RunWith(Parameterized::class)
+class InvalidCasesTest(val name: String, val errorMsg: String) {
+    companion object {
+        @JvmStatic
+        @Parameterized.Parameters(name = "failingCase({0})")
+        fun data() : Collection<Array<Any>> = listOf(
+                arrayOf<Any>("foo.InvalidFirstArg1", LifecycleProcessor.INVALID_FIRST_ARGUMENT),
+                arrayOf<Any>("foo.InvalidFirstArg2", LifecycleProcessor.INVALID_FIRST_ARGUMENT),
+                arrayOf<Any>("foo.InvalidSecondArg", LifecycleProcessor.INVALID_SECOND_ARGUMENT),
+                arrayOf<Any>("foo.TooManyArgs1", LifecycleProcessor.TOO_MANY_ARGS),
+                arrayOf<Any>("foo.TooManyArgs2", LifecycleProcessor.TOO_MANY_ARGS_NOT_ON_ANY),
+                arrayOf<Any>("foo.InvalidMethodModifier",
+                        LifecycleProcessor.INVALID_METHOD_MODIFIER),
+                arrayOf<Any>("foo.InvalidClassModifier", LifecycleProcessor.INVALID_CLASS_MODIFIER),
+                arrayOf<Any>("foo.InvalidInheritance1",
+                        LifecycleProcessor.INVALID_STATE_OVERRIDE_METHOD),
+                arrayOf<Any>("foo.InvalidInheritance2",
+                        LifecycleProcessor.INVALID_STATE_OVERRIDE_METHOD)
+        )
+    }
+
+    @Test
+    fun shouldFailWithError() {
+        processClass(name).failsToCompile().withErrorContaining(errorMsg)
+    }
+}
diff --git a/lifecycle/compiler/src/tests/kotlin/android/arch/lifecycle/ValidCasesTest.kt b/lifecycle/compiler/src/tests/kotlin/android/arch/lifecycle/ValidCasesTest.kt
new file mode 100644
index 0000000..66429a2
--- /dev/null
+++ b/lifecycle/compiler/src/tests/kotlin/android/arch/lifecycle/ValidCasesTest.kt
@@ -0,0 +1,96 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.arch.lifecycle
+
+import android.arch.lifecycle.utils.load
+import android.arch.lifecycle.utils.processClass
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.junit.runners.JUnit4
+
+@RunWith(JUnit4::class)
+class ValidCasesTest {
+    @Test
+    fun testTest() {
+        processClass("foo.Bar").compilesWithoutError()
+    }
+
+    @Test
+    fun testOnAny() {
+        processClass("foo.OnAnyMethod").compilesWithoutError().and().generatesSources(
+                load("foo.OnAnyMethod_LifecycleAdapter", "expected")
+        )
+    }
+
+    @Test
+    fun testInheritance() {
+        processClass("foo.InheritanceOk1").compilesWithoutError()
+    }
+
+    @Test
+    fun testInheritance2() {
+        processClass("foo.InheritanceOk2").compilesWithoutError().and().generatesSources(
+                load("foo.InheritanceOk2Base_LifecycleAdapter", "expected"),
+                load("foo.InheritanceOk2Derived_LifecycleAdapter", "expected")
+        )
+    }
+
+    @Test
+    fun testInheritance3() {
+        processClass("foo.InheritanceOk3").compilesWithoutError().and().generatesSources(
+                load("foo.InheritanceOk3Base_LifecycleAdapter", "expected"),
+                load("foo.InheritanceOk3Derived_LifecycleAdapter", "expected")
+        )
+    }
+
+    @Test
+    fun testNoPackageClass() {
+        processClass("NoPackageOk").compilesWithoutError()
+    }
+
+    @Test
+    fun testInterface1(){
+        processClass("foo.InterfaceOk1").compilesWithoutError()
+    }
+
+    @Test
+    fun testInterface2() {
+        processClass("foo.InterfaceOk2").compilesWithoutError().and().generatesSources(
+                load("foo.InterfaceOk2Base_LifecycleAdapter", "expected"),
+                load("foo.InterfaceOk2Derived_LifecycleAdapter", "expected"),
+                load("foo.InterfaceOk2Interface_LifecycleAdapter", "expected")
+        )
+    }
+
+    @Test
+    fun testInheritanceDifferentPackages1() {
+        processClass("foo.DifferentPackagesBase1",
+                "bar.DifferentPackagesDerived1").compilesWithoutError().and().generatesSources(
+                load("foo.DifferentPackagesBase1_LifecycleAdapter", "expected"),
+                load("bar.DifferentPackagesDerived1_LifecycleAdapter", "expected")
+        )
+    }
+
+    @Test
+    fun testInheritanceDifferentPackages2() {
+        processClass("foo.DifferentPackagesBase2",
+                "bar.DifferentPackagesDerived2").compilesWithoutError().and().generatesSources(
+                load("foo.DifferentPackagesBase2_LifecycleAdapter", "expected"),
+                load("bar.DifferentPackagesDerived2_LifecycleAdapter", "expected")
+        )
+    }
+}
diff --git a/lifecycle/compiler/src/tests/kotlin/android/arch/lifecycle/utils/TestUtils.kt b/lifecycle/compiler/src/tests/kotlin/android/arch/lifecycle/utils/TestUtils.kt
new file mode 100644
index 0000000..8a051b4
--- /dev/null
+++ b/lifecycle/compiler/src/tests/kotlin/android/arch/lifecycle/utils/TestUtils.kt
@@ -0,0 +1,40 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.arch.lifecycle.utils
+
+import android.arch.lifecycle.LifecycleProcessor
+import com.google.common.truth.Truth
+import com.google.testing.compile.CompileTester
+import com.google.testing.compile.JavaFileObjects
+import com.google.testing.compile.JavaSourceSubjectFactory
+import com.google.testing.compile.JavaSourcesSubject
+import java.io.File
+import java.nio.charset.Charset
+import javax.tools.JavaFileObject
+
+fun load(fullClassName: String, folder: String): JavaFileObject {
+    val folderPath = "src/tests/test-data/${if (folder.isEmpty()) "" else folder + "/"}"
+    val split = fullClassName.split(".")
+    val code = File("$folderPath/${split.last()}.java").readText(Charset.defaultCharset())
+    return JavaFileObjects.forSourceString(fullClassName, code)
+}
+
+fun processClass(vararg fullClassNames: String): CompileTester {
+    val processedWith = JavaSourcesSubject.assertThat(
+            *fullClassNames.map { load(it, "") }.toTypedArray()).processedWith(LifecycleProcessor())
+    return checkNotNull(processedWith)
+}
diff --git a/lifecycle/compiler/src/tests/test-data/Bar.java b/lifecycle/compiler/src/tests/test-data/Bar.java
new file mode 100644
index 0000000..773948e
--- /dev/null
+++ b/lifecycle/compiler/src/tests/test-data/Bar.java
@@ -0,0 +1,40 @@
+package foo;
+
+import static android.arch.lifecycle.Lifecycle.Event.ON_START;
+import static android.arch.lifecycle.Lifecycle.Event.ON_STOP;
+
+import android.arch.lifecycle.Lifecycle.Event;
+import android.arch.lifecycle.LifecycleOwner;
+import android.arch.lifecycle.OnLifecycleEvent;
+
+public class Bar {
+    @OnLifecycleEvent(ON_START)
+    public void doOnStart() {
+    }
+
+    @OnLifecycleEvent(ON_STOP)
+    public void doOnStop1Arg(LifecycleOwner provider) {
+    }
+
+    @OnLifecycleEvent(ON_STOP)
+    public void doOnStop2Args(LifecycleOwner provider) {
+    }
+
+    public static class Inner1 {
+        @OnLifecycleEvent(ON_START)
+        public void doOnStart() {
+        }
+
+        public static class Inner2 {
+            @OnLifecycleEvent(ON_START)
+            public void doOnStart() {
+            }
+
+            public static class Inner3 {
+                @OnLifecycleEvent(ON_START)
+                public void doOnStart() {
+                }
+            }
+        }
+    }
+}
diff --git a/lifecycle/compiler/src/tests/test-data/DifferentPackagesBase1.java b/lifecycle/compiler/src/tests/test-data/DifferentPackagesBase1.java
new file mode 100644
index 0000000..cc9d5d5
--- /dev/null
+++ b/lifecycle/compiler/src/tests/test-data/DifferentPackagesBase1.java
@@ -0,0 +1,28 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package foo;
+
+import static android.arch.lifecycle.Lifecycle.Event.ON_STOP;
+
+import android.arch.lifecycle.Lifecycle.Event;
+import android.arch.lifecycle.LifecycleOwner;
+import android.arch.lifecycle.OnLifecycleEvent;
+
+public class DifferentPackagesBase1 {
+    @OnLifecycleEvent(ON_STOP)
+    void onStop(LifecycleOwner provider){}
+}
diff --git a/lifecycle/compiler/src/tests/test-data/DifferentPackagesBase2.java b/lifecycle/compiler/src/tests/test-data/DifferentPackagesBase2.java
new file mode 100644
index 0000000..e31921a
--- /dev/null
+++ b/lifecycle/compiler/src/tests/test-data/DifferentPackagesBase2.java
@@ -0,0 +1,33 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package foo;
+
+import static android.arch.lifecycle.Lifecycle.Event.ON_STOP;
+
+import android.arch.lifecycle.Lifecycle.Event;
+import android.arch.lifecycle.LifecycleOwner;
+import android.arch.lifecycle.OnLifecycleEvent;
+
+class DifferentPackagesPreBase2 {
+    @OnLifecycleEvent(ON_STOP)
+    void onStop(LifecycleOwner provider){}
+}
+
+public class DifferentPackagesBase2 extends DifferentPackagesPreBase2 {
+    @OnLifecycleEvent(ON_STOP)
+    public void onStop(LifecycleOwner provider){}
+}
diff --git a/lifecycle/compiler/src/tests/test-data/DifferentPackagesDerived1.java b/lifecycle/compiler/src/tests/test-data/DifferentPackagesDerived1.java
new file mode 100644
index 0000000..8ad4c91
--- /dev/null
+++ b/lifecycle/compiler/src/tests/test-data/DifferentPackagesDerived1.java
@@ -0,0 +1,29 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package bar;
+
+import static android.arch.lifecycle.Lifecycle.Event.ON_STOP;
+
+import android.arch.lifecycle.Lifecycle.Event;
+import android.arch.lifecycle.LifecycleOwner;
+import android.arch.lifecycle.OnLifecycleEvent;
+import foo.DifferentPackagesBase1;
+
+public class DifferentPackagesDerived1 extends DifferentPackagesBase1 {
+    @OnLifecycleEvent(ON_STOP)
+    void onStop2(LifecycleOwner provider){}
+}
diff --git a/lifecycle/compiler/src/tests/test-data/DifferentPackagesDerived2.java b/lifecycle/compiler/src/tests/test-data/DifferentPackagesDerived2.java
new file mode 100644
index 0000000..648f628
--- /dev/null
+++ b/lifecycle/compiler/src/tests/test-data/DifferentPackagesDerived2.java
@@ -0,0 +1,29 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package bar;
+
+import static android.arch.lifecycle.Lifecycle.Event.ON_STOP;
+
+import android.arch.lifecycle.Lifecycle.Event;
+import android.arch.lifecycle.LifecycleOwner;
+import android.arch.lifecycle.OnLifecycleEvent;
+import foo.DifferentPackagesBase2;
+
+public class DifferentPackagesDerived2 extends DifferentPackagesBase2 {
+    @OnLifecycleEvent(ON_STOP)
+    void onStop2(LifecycleOwner provider){}
+}
diff --git a/lifecycle/compiler/src/tests/test-data/IGNORE_CHECKSTYLE b/lifecycle/compiler/src/tests/test-data/IGNORE_CHECKSTYLE
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/lifecycle/compiler/src/tests/test-data/IGNORE_CHECKSTYLE
diff --git a/lifecycle/compiler/src/tests/test-data/InheritanceOk1.java b/lifecycle/compiler/src/tests/test-data/InheritanceOk1.java
new file mode 100644
index 0000000..79c2151
--- /dev/null
+++ b/lifecycle/compiler/src/tests/test-data/InheritanceOk1.java
@@ -0,0 +1,56 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package foo;
+
+import static android.arch.lifecycle.Lifecycle.Event.ON_STOP;
+
+import android.arch.lifecycle.Lifecycle.Event;
+import android.arch.lifecycle.LifecycleOwner;
+import android.arch.lifecycle.OnLifecycleEvent;
+
+class Base1 {
+    @OnLifecycleEvent(ON_STOP)
+    public void onStop(LifecycleOwner provider) {
+    }
+}
+
+class Proxy extends Base1 {
+}
+
+class Derived1 extends Proxy {
+    @OnLifecycleEvent(ON_STOP)
+    public void onStop2(LifecycleOwner provider) {
+    }
+}
+
+class Derived2 extends Proxy {
+    @OnLifecycleEvent(ON_STOP)
+    public void onStop2(LifecycleOwner provider) {
+    }
+}
+
+class Base2 {
+    @OnLifecycleEvent(ON_STOP)
+    public void onStop(LifecycleOwner provider) {
+    }
+}
+
+class Derived3 extends Base2 {
+    @OnLifecycleEvent(ON_STOP)
+    public void onStop2(LifecycleOwner provider) {
+    }
+}
diff --git a/lifecycle/compiler/src/tests/test-data/InheritanceOk2.java b/lifecycle/compiler/src/tests/test-data/InheritanceOk2.java
new file mode 100644
index 0000000..31f0a41
--- /dev/null
+++ b/lifecycle/compiler/src/tests/test-data/InheritanceOk2.java
@@ -0,0 +1,19 @@
+package foo;
+
+import static android.arch.lifecycle.Lifecycle.Event.ON_STOP;
+
+import android.arch.lifecycle.Lifecycle.Event;
+import android.arch.lifecycle.LifecycleOwner;
+import android.arch.lifecycle.OnLifecycleEvent;
+
+class InheritanceOk2Base {
+    @OnLifecycleEvent(ON_STOP)
+    public void onStop(LifecycleOwner provider) {
+    }
+}
+
+class InheritanceOk2Derived extends InheritanceOk2Base {
+    @OnLifecycleEvent(ON_STOP)
+    public void onStop2(LifecycleOwner provider) {
+    }
+}
diff --git a/lifecycle/compiler/src/tests/test-data/InheritanceOk3.java b/lifecycle/compiler/src/tests/test-data/InheritanceOk3.java
new file mode 100644
index 0000000..50c4623
--- /dev/null
+++ b/lifecycle/compiler/src/tests/test-data/InheritanceOk3.java
@@ -0,0 +1,35 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package foo;
+
+import static android.arch.lifecycle.Lifecycle.Event.ON_STOP;
+
+import android.arch.lifecycle.Lifecycle.Event;
+import android.arch.lifecycle.LifecycleOwner;
+import android.arch.lifecycle.OnLifecycleEvent;
+
+class InheritanceOk3Base {
+    @OnLifecycleEvent(ON_STOP)
+    public void onStop(LifecycleOwner provider) {
+    }
+}
+
+class InheritanceOk3Derived extends InheritanceOk3Base {
+    @OnLifecycleEvent(ON_STOP)
+    public void onStop(LifecycleOwner provider) {
+    }
+}
diff --git a/lifecycle/compiler/src/tests/test-data/InterfaceOk1.java b/lifecycle/compiler/src/tests/test-data/InterfaceOk1.java
new file mode 100644
index 0000000..3584f6d
--- /dev/null
+++ b/lifecycle/compiler/src/tests/test-data/InterfaceOk1.java
@@ -0,0 +1,26 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+import static android.arch.lifecycle.Lifecycle.Event.ON_STOP;
+
+import android.arch.lifecycle.Lifecycle.Event;
+import android.arch.lifecycle.LifecycleOwner;
+import android.arch.lifecycle.OnLifecycleEvent;
+
+interface InterfaceOk1 {
+    @OnLifecycleEvent(ON_STOP)
+    void onStop(LifecycleOwner provider);
+}
diff --git a/lifecycle/compiler/src/tests/test-data/InterfaceOk2.java b/lifecycle/compiler/src/tests/test-data/InterfaceOk2.java
new file mode 100644
index 0000000..58688d3
--- /dev/null
+++ b/lifecycle/compiler/src/tests/test-data/InterfaceOk2.java
@@ -0,0 +1,45 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package foo;
+
+import static android.arch.lifecycle.Lifecycle.Event.ON_STOP;
+
+import android.arch.lifecycle.Lifecycle.Event;
+import android.arch.lifecycle.LifecycleOwner;
+import android.arch.lifecycle.OnLifecycleEvent;
+
+class InterfaceOk2Base {
+    @OnLifecycleEvent(ON_STOP)
+    public void onStop1(LifecycleOwner provider) {
+    }
+}
+
+interface InterfaceOk2Interface {
+    @OnLifecycleEvent(ON_STOP)
+    void onStop2(LifecycleOwner provider);
+}
+
+class InterfaceOk2Proxy extends InterfaceOk2Base {
+}
+
+class InterfaceOk2Derived extends InterfaceOk2Proxy implements InterfaceOk2Interface {
+    @OnLifecycleEvent(ON_STOP)
+    public void onStop3(LifecycleOwner provider) {
+    }
+
+    public void onStop2(LifecycleOwner provider) {}
+}
diff --git a/lifecycle/compiler/src/tests/test-data/InvalidClassModifier.java b/lifecycle/compiler/src/tests/test-data/InvalidClassModifier.java
new file mode 100644
index 0000000..4c30bb6
--- /dev/null
+++ b/lifecycle/compiler/src/tests/test-data/InvalidClassModifier.java
@@ -0,0 +1,30 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package foo;
+
+import static android.arch.lifecycle.Lifecycle.Event.ON_STOP;
+
+import android.arch.lifecycle.Lifecycle.Event;
+import android.arch.lifecycle.OnLifecycleEvent;
+
+public class InvalidClassModifier {
+    private static class Inner {
+        @OnLifecycleEvent(ON_STOP)
+        private void onStop() {
+        }
+    }
+}
diff --git a/lifecycle/compiler/src/tests/test-data/InvalidFirstArg1.java b/lifecycle/compiler/src/tests/test-data/InvalidFirstArg1.java
new file mode 100644
index 0000000..26bfc23
--- /dev/null
+++ b/lifecycle/compiler/src/tests/test-data/InvalidFirstArg1.java
@@ -0,0 +1,12 @@
+package foo;
+
+import static android.arch.lifecycle.Lifecycle.Event.ON_STOP;
+
+import android.arch.lifecycle.Lifecycle.Event;
+import android.arch.lifecycle.OnLifecycleEvent;
+
+public class InvalidFirstArg1 {
+    @OnLifecycleEvent(ON_STOP)
+    public void onStop(Event event) {
+    }
+}
diff --git a/lifecycle/compiler/src/tests/test-data/InvalidFirstArg2.java b/lifecycle/compiler/src/tests/test-data/InvalidFirstArg2.java
new file mode 100644
index 0000000..459c316
--- /dev/null
+++ b/lifecycle/compiler/src/tests/test-data/InvalidFirstArg2.java
@@ -0,0 +1,12 @@
+package foo;
+
+import static android.arch.lifecycle.Lifecycle.Event.ON_ANY;
+
+import android.arch.lifecycle.Lifecycle.Event;
+import android.arch.lifecycle.OnLifecycleEvent;
+
+public class InvalidFirstArg2 {
+    @OnLifecycleEvent(ON_ANY)
+    public void onStop(Event e2, Event event) {
+    }
+}
diff --git a/lifecycle/compiler/src/tests/test-data/InvalidInheritance1.java b/lifecycle/compiler/src/tests/test-data/InvalidInheritance1.java
new file mode 100644
index 0000000..69f6bb2
--- /dev/null
+++ b/lifecycle/compiler/src/tests/test-data/InvalidInheritance1.java
@@ -0,0 +1,34 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package foo;
+
+import static android.arch.lifecycle.Lifecycle.Event.ON_START;
+import static android.arch.lifecycle.Lifecycle.Event.ON_STOP;
+
+import android.arch.lifecycle.OnLifecycleEvent;
+
+class Base {
+    @OnLifecycleEvent(ON_STOP)
+    void foo() {
+    }
+}
+
+class Derived extends Base {
+    @OnLifecycleEvent(ON_START)
+    void foo() {
+    }
+}
diff --git a/lifecycle/compiler/src/tests/test-data/InvalidInheritance2.java b/lifecycle/compiler/src/tests/test-data/InvalidInheritance2.java
new file mode 100644
index 0000000..b714e47
--- /dev/null
+++ b/lifecycle/compiler/src/tests/test-data/InvalidInheritance2.java
@@ -0,0 +1,33 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package foo;
+
+import static android.arch.lifecycle.Lifecycle.Event.ON_START;
+import static android.arch.lifecycle.Lifecycle.Event.ON_STOP;
+
+import android.arch.lifecycle.LifecycleOwner;
+import android.arch.lifecycle.OnLifecycleEvent;
+
+interface Base {
+    @OnLifecycleEvent(ON_STOP)
+    void foo();
+}
+
+class Derived implements Base {
+    @OnLifecycleEvent(ON_START)
+    public void foo(){}
+}
diff --git a/lifecycle/compiler/src/tests/test-data/InvalidMethodModifier.java b/lifecycle/compiler/src/tests/test-data/InvalidMethodModifier.java
new file mode 100644
index 0000000..6540333
--- /dev/null
+++ b/lifecycle/compiler/src/tests/test-data/InvalidMethodModifier.java
@@ -0,0 +1,28 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package foo;
+
+import static android.arch.lifecycle.Lifecycle.Event.ON_STOP;
+
+import android.arch.lifecycle.Lifecycle.Event;
+import android.arch.lifecycle.OnLifecycleEvent;
+
+public class InvalidMethodModifier {
+    @OnLifecycleEvent(ON_STOP)
+    private void onStop() {
+    }
+}
diff --git a/lifecycle/compiler/src/tests/test-data/InvalidSecondArg.java b/lifecycle/compiler/src/tests/test-data/InvalidSecondArg.java
new file mode 100644
index 0000000..e98d915
--- /dev/null
+++ b/lifecycle/compiler/src/tests/test-data/InvalidSecondArg.java
@@ -0,0 +1,12 @@
+package foo;
+
+import static android.arch.lifecycle.Lifecycle.Event.ON_ANY;
+
+import android.arch.lifecycle.LifecycleOwner;
+import android.arch.lifecycle.OnLifecycleEvent;
+
+public class InvalidSecondArg {
+    @OnLifecycleEvent(ON_ANY)
+    public void onStop(LifecycleOwner provider, Object lastEvent) {
+    }
+}
diff --git a/lifecycle/compiler/src/tests/test-data/NoPackageOk.java b/lifecycle/compiler/src/tests/test-data/NoPackageOk.java
new file mode 100644
index 0000000..a00283d
--- /dev/null
+++ b/lifecycle/compiler/src/tests/test-data/NoPackageOk.java
@@ -0,0 +1,26 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+import static android.arch.lifecycle.Lifecycle.Event.ON_STOP;
+
+import android.arch.lifecycle.Lifecycle.Event;
+import android.arch.lifecycle.LifecycleOwner;
+import android.arch.lifecycle.OnLifecycleEvent;
+
+class NoPackageOk {
+    @OnLifecycleEvent(ON_STOP)
+    void onStop(LifecycleOwner provider){}
+}
diff --git a/lifecycle/compiler/src/tests/test-data/OnAnyMethod.java b/lifecycle/compiler/src/tests/test-data/OnAnyMethod.java
new file mode 100644
index 0000000..f03f1cd
--- /dev/null
+++ b/lifecycle/compiler/src/tests/test-data/OnAnyMethod.java
@@ -0,0 +1,37 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package foo;
+
+import static android.arch.lifecycle.Lifecycle.Event.ON_ANY;
+import static android.arch.lifecycle.Lifecycle.Event.ON_STOP;
+
+import android.arch.lifecycle.Lifecycle.Event;
+import android.arch.lifecycle.LifecycleOwner;
+import android.arch.lifecycle.OnLifecycleEvent;
+
+public class OnAnyMethod {
+
+    @OnLifecycleEvent(ON_STOP)
+    void onStop(LifecycleOwner provider){}
+
+    @OnLifecycleEvent(ON_ANY)
+    void any(LifecycleOwner provider){}
+
+
+    @OnLifecycleEvent(ON_ANY)
+    void any(LifecycleOwner provider, Event event){}
+}
diff --git a/lifecycle/compiler/src/tests/test-data/TooManyArgs1.java b/lifecycle/compiler/src/tests/test-data/TooManyArgs1.java
new file mode 100644
index 0000000..0d65098
--- /dev/null
+++ b/lifecycle/compiler/src/tests/test-data/TooManyArgs1.java
@@ -0,0 +1,13 @@
+package foo;
+
+import static android.arch.lifecycle.Lifecycle.Event.ON_ANY;
+
+import android.arch.lifecycle.Lifecycle.Event;
+import android.arch.lifecycle.LifecycleOwner;
+import android.arch.lifecycle.OnLifecycleEvent;
+
+public class TooManyArgs1 {
+    @OnLifecycleEvent(ON_ANY)
+    public void onAny(LifecycleOwner provider, Event event, int x) {
+    }
+}
diff --git a/lifecycle/compiler/src/tests/test-data/TooManyArgs2.java b/lifecycle/compiler/src/tests/test-data/TooManyArgs2.java
new file mode 100644
index 0000000..f332d0b
--- /dev/null
+++ b/lifecycle/compiler/src/tests/test-data/TooManyArgs2.java
@@ -0,0 +1,13 @@
+package foo;
+
+import static android.arch.lifecycle.Lifecycle.Event.ON_STOP;
+
+import android.arch.lifecycle.Lifecycle.Event;
+import android.arch.lifecycle.LifecycleOwner;
+import android.arch.lifecycle.OnLifecycleEvent;
+
+public class TooManyArgs2 {
+    @OnLifecycleEvent(ON_STOP)
+    public void onStop(LifecycleOwner provider, Event event) {
+    }
+}
diff --git a/lifecycle/compiler/src/tests/test-data/expected/DifferentPackagesBase1_LifecycleAdapter.java b/lifecycle/compiler/src/tests/test-data/expected/DifferentPackagesBase1_LifecycleAdapter.java
new file mode 100644
index 0000000..0728bf5
--- /dev/null
+++ b/lifecycle/compiler/src/tests/test-data/expected/DifferentPackagesBase1_LifecycleAdapter.java
@@ -0,0 +1,46 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package foo;
+
+import android.arch.lifecycle.GenericLifecycleObserver;
+import android.arch.lifecycle.Lifecycle;
+import android.arch.lifecycle.LifecycleOwner;
+import java.lang.Object;
+import java.lang.Override;
+
+public class DifferentPackagesBase1_LifecycleAdapter implements GenericLifecycleObserver {
+  final DifferentPackagesBase1 mReceiver;
+
+  DifferentPackagesBase1_LifecycleAdapter(DifferentPackagesBase1 receiver) {
+    this.mReceiver = receiver;
+  }
+
+  @Override
+  public void onStateChanged(LifecycleOwner owner, Lifecycle.Event event) {
+    if (event == Lifecycle.Event.ON_STOP) {
+      mReceiver.onStop(owner);
+    }
+  }
+
+  public Object getReceiver() {
+    return mReceiver;
+  }
+
+  public static void __synthetic_onStop(DifferentPackagesBase1 receiver, LifecycleOwner owner) {
+    receiver.onStop(owner);
+  }
+}
diff --git a/lifecycle/compiler/src/tests/test-data/expected/DifferentPackagesBase2_LifecycleAdapter.java b/lifecycle/compiler/src/tests/test-data/expected/DifferentPackagesBase2_LifecycleAdapter.java
new file mode 100644
index 0000000..cb6d6d3
--- /dev/null
+++ b/lifecycle/compiler/src/tests/test-data/expected/DifferentPackagesBase2_LifecycleAdapter.java
@@ -0,0 +1,42 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package foo;
+
+import android.arch.lifecycle.GenericLifecycleObserver;
+import android.arch.lifecycle.Lifecycle;
+import android.arch.lifecycle.LifecycleOwner;
+import java.lang.Object;
+import java.lang.Override;
+
+public class DifferentPackagesBase2_LifecycleAdapter implements GenericLifecycleObserver {
+  final DifferentPackagesBase2 mReceiver;
+
+  DifferentPackagesBase2_LifecycleAdapter(DifferentPackagesBase2 receiver) {
+    this.mReceiver = receiver;
+  }
+
+  @Override
+  public void onStateChanged(LifecycleOwner owner, Lifecycle.Event event) {
+    if (event == Lifecycle.Event.ON_STOP) {
+      mReceiver.onStop(owner);
+    }
+  }
+
+  public Object getReceiver() {
+    return mReceiver;
+  }
+}
diff --git a/lifecycle/compiler/src/tests/test-data/expected/DifferentPackagesDerived1_LifecycleAdapter.java b/lifecycle/compiler/src/tests/test-data/expected/DifferentPackagesDerived1_LifecycleAdapter.java
new file mode 100644
index 0000000..fe6dd24
--- /dev/null
+++ b/lifecycle/compiler/src/tests/test-data/expected/DifferentPackagesDerived1_LifecycleAdapter.java
@@ -0,0 +1,44 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package bar;
+
+import android.arch.lifecycle.GenericLifecycleObserver;
+import android.arch.lifecycle.Lifecycle;
+import android.arch.lifecycle.LifecycleOwner;
+import foo.DifferentPackagesBase1_LifecycleAdapter;
+import java.lang.Object;
+import java.lang.Override;
+
+public class DifferentPackagesDerived1_LifecycleAdapter implements GenericLifecycleObserver {
+  final DifferentPackagesDerived1 mReceiver;
+
+  DifferentPackagesDerived1_LifecycleAdapter(DifferentPackagesDerived1 receiver) {
+    this.mReceiver = receiver;
+  }
+
+  @Override
+  public void onStateChanged(LifecycleOwner owner, Lifecycle.Event event) {
+    if (event == Lifecycle.Event.ON_STOP) {
+      DifferentPackagesBase1_LifecycleAdapter.__synthetic_onStop(mReceiver,owner);
+      mReceiver.onStop2(owner);
+    }
+  }
+
+  public Object getReceiver() {
+    return mReceiver;
+  }
+}
diff --git a/lifecycle/compiler/src/tests/test-data/expected/DifferentPackagesDerived2_LifecycleAdapter.java b/lifecycle/compiler/src/tests/test-data/expected/DifferentPackagesDerived2_LifecycleAdapter.java
new file mode 100644
index 0000000..261dce1
--- /dev/null
+++ b/lifecycle/compiler/src/tests/test-data/expected/DifferentPackagesDerived2_LifecycleAdapter.java
@@ -0,0 +1,43 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package bar;
+
+import android.arch.lifecycle.GenericLifecycleObserver;
+import android.arch.lifecycle.Lifecycle;
+import android.arch.lifecycle.LifecycleOwner;
+import java.lang.Object;
+import java.lang.Override;
+
+public class DifferentPackagesDerived2_LifecycleAdapter implements GenericLifecycleObserver {
+  final DifferentPackagesDerived2 mReceiver;
+
+  DifferentPackagesDerived2_LifecycleAdapter(DifferentPackagesDerived2 receiver) {
+    this.mReceiver = receiver;
+  }
+
+  @Override
+  public void onStateChanged(LifecycleOwner owner, Lifecycle.Event event) {
+    if (event == Lifecycle.Event.ON_STOP) {
+      mReceiver.onStop(owner);
+      mReceiver.onStop2(owner);
+    }
+  }
+
+  public Object getReceiver() {
+    return mReceiver;
+  }
+}
diff --git a/lifecycle/compiler/src/tests/test-data/expected/InheritanceOk2Base_LifecycleAdapter.java b/lifecycle/compiler/src/tests/test-data/expected/InheritanceOk2Base_LifecycleAdapter.java
new file mode 100644
index 0000000..3712e48
--- /dev/null
+++ b/lifecycle/compiler/src/tests/test-data/expected/InheritanceOk2Base_LifecycleAdapter.java
@@ -0,0 +1,42 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package foo;
+
+import android.arch.lifecycle.GenericLifecycleObserver;
+import android.arch.lifecycle.Lifecycle;
+import android.arch.lifecycle.LifecycleOwner;
+import java.lang.Object;
+import java.lang.Override;
+
+public class InheritanceOk2Base_LifecycleAdapter implements GenericLifecycleObserver {
+  final InheritanceOk2Base mReceiver;
+
+  InheritanceOk2Base_LifecycleAdapter(InheritanceOk2Base receiver) {
+    this.mReceiver = receiver;
+  }
+
+  @Override
+  public void onStateChanged(LifecycleOwner owner, Lifecycle.Event event) {
+    if (event == Lifecycle.Event.ON_STOP) {
+      mReceiver.onStop(owner);
+    }
+  }
+
+  public Object getReceiver() {
+    return mReceiver;
+  }
+}
diff --git a/lifecycle/compiler/src/tests/test-data/expected/InheritanceOk2Derived_LifecycleAdapter.java b/lifecycle/compiler/src/tests/test-data/expected/InheritanceOk2Derived_LifecycleAdapter.java
new file mode 100644
index 0000000..1549387
--- /dev/null
+++ b/lifecycle/compiler/src/tests/test-data/expected/InheritanceOk2Derived_LifecycleAdapter.java
@@ -0,0 +1,43 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package foo;
+
+import android.arch.lifecycle.GenericLifecycleObserver;
+import android.arch.lifecycle.Lifecycle;
+import android.arch.lifecycle.LifecycleOwner;
+import java.lang.Object;
+import java.lang.Override;
+
+public class InheritanceOk2Derived_LifecycleAdapter implements GenericLifecycleObserver {
+  final InheritanceOk2Derived mReceiver;
+
+  InheritanceOk2Derived_LifecycleAdapter(InheritanceOk2Derived receiver) {
+    this.mReceiver = receiver;
+  }
+
+  @Override
+  public void onStateChanged(LifecycleOwner owner, Lifecycle.Event event) {
+    if (event == Lifecycle.Event.ON_STOP) {
+      mReceiver.onStop(owner);
+      mReceiver.onStop2(owner);
+    }
+  }
+
+  public Object getReceiver() {
+    return mReceiver;
+  }
+}
diff --git a/lifecycle/compiler/src/tests/test-data/expected/InheritanceOk3Base_LifecycleAdapter.java b/lifecycle/compiler/src/tests/test-data/expected/InheritanceOk3Base_LifecycleAdapter.java
new file mode 100644
index 0000000..1647443
--- /dev/null
+++ b/lifecycle/compiler/src/tests/test-data/expected/InheritanceOk3Base_LifecycleAdapter.java
@@ -0,0 +1,42 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package foo;
+
+import android.arch.lifecycle.GenericLifecycleObserver;
+import android.arch.lifecycle.Lifecycle;
+import android.arch.lifecycle.LifecycleOwner;
+import java.lang.Object;
+import java.lang.Override;
+
+public class InheritanceOk3Base_LifecycleAdapter implements GenericLifecycleObserver {
+  final InheritanceOk3Base mReceiver;
+
+  InheritanceOk3Base_LifecycleAdapter(InheritanceOk3Base receiver) {
+    this.mReceiver = receiver;
+  }
+
+  @Override
+  public void onStateChanged(LifecycleOwner owner, Lifecycle.Event event) {
+    if (event == Lifecycle.Event.ON_STOP) {
+      mReceiver.onStop(owner);
+    }
+  }
+
+  public Object getReceiver() {
+    return mReceiver;
+  }
+}
diff --git a/lifecycle/compiler/src/tests/test-data/expected/InheritanceOk3Derived_LifecycleAdapter.java b/lifecycle/compiler/src/tests/test-data/expected/InheritanceOk3Derived_LifecycleAdapter.java
new file mode 100644
index 0000000..e41b9eb
--- /dev/null
+++ b/lifecycle/compiler/src/tests/test-data/expected/InheritanceOk3Derived_LifecycleAdapter.java
@@ -0,0 +1,42 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package foo;
+
+import android.arch.lifecycle.GenericLifecycleObserver;
+import android.arch.lifecycle.Lifecycle;
+import android.arch.lifecycle.LifecycleOwner;
+import java.lang.Object;
+import java.lang.Override;
+
+public class InheritanceOk3Derived_LifecycleAdapter implements GenericLifecycleObserver {
+  final InheritanceOk3Derived mReceiver;
+
+  InheritanceOk3Derived_LifecycleAdapter(InheritanceOk3Derived receiver) {
+    this.mReceiver = receiver;
+  }
+
+  @Override
+  public void onStateChanged(LifecycleOwner owner, Lifecycle.Event event) {
+    if (event == Lifecycle.Event.ON_STOP) {
+      mReceiver.onStop(owner);
+    }
+  }
+
+  public Object getReceiver() {
+    return mReceiver;
+  }
+}
diff --git a/lifecycle/compiler/src/tests/test-data/expected/InterfaceOk2Base_LifecycleAdapter.java b/lifecycle/compiler/src/tests/test-data/expected/InterfaceOk2Base_LifecycleAdapter.java
new file mode 100644
index 0000000..22de775
--- /dev/null
+++ b/lifecycle/compiler/src/tests/test-data/expected/InterfaceOk2Base_LifecycleAdapter.java
@@ -0,0 +1,42 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package foo;
+
+import android.arch.lifecycle.GenericLifecycleObserver;
+import android.arch.lifecycle.Lifecycle;
+import android.arch.lifecycle.LifecycleOwner;
+import java.lang.Object;
+import java.lang.Override;
+
+public class InterfaceOk2Base_LifecycleAdapter implements GenericLifecycleObserver {
+  final InterfaceOk2Base mReceiver;
+
+  InterfaceOk2Base_LifecycleAdapter(InterfaceOk2Base receiver) {
+    this.mReceiver = receiver;
+  }
+
+  @Override
+  public void onStateChanged(LifecycleOwner owner, Lifecycle.Event event) {
+    if (event == Lifecycle.Event.ON_STOP) {
+      mReceiver.onStop1(owner);
+    }
+  }
+
+  public Object getReceiver() {
+    return mReceiver;
+  }
+}
diff --git a/lifecycle/compiler/src/tests/test-data/expected/InterfaceOk2Derived_LifecycleAdapter.java b/lifecycle/compiler/src/tests/test-data/expected/InterfaceOk2Derived_LifecycleAdapter.java
new file mode 100644
index 0000000..9ba0461
--- /dev/null
+++ b/lifecycle/compiler/src/tests/test-data/expected/InterfaceOk2Derived_LifecycleAdapter.java
@@ -0,0 +1,44 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package foo;
+
+import android.arch.lifecycle.GenericLifecycleObserver;
+import android.arch.lifecycle.Lifecycle;
+import android.arch.lifecycle.LifecycleOwner;
+import java.lang.Object;
+import java.lang.Override;
+
+public class InterfaceOk2Derived_LifecycleAdapter implements GenericLifecycleObserver {
+  final InterfaceOk2Derived mReceiver;
+
+  InterfaceOk2Derived_LifecycleAdapter(InterfaceOk2Derived receiver) {
+    this.mReceiver = receiver;
+  }
+
+  @Override
+  public void onStateChanged(LifecycleOwner owner, Lifecycle.Event event) {
+    if (event == Lifecycle.Event.ON_STOP) {
+      mReceiver.onStop1(owner);
+      mReceiver.onStop2(owner);
+      mReceiver.onStop3(owner);
+    }
+  }
+
+  public Object getReceiver() {
+    return mReceiver;
+  }
+}
diff --git a/lifecycle/compiler/src/tests/test-data/expected/InterfaceOk2Interface_LifecycleAdapter.java b/lifecycle/compiler/src/tests/test-data/expected/InterfaceOk2Interface_LifecycleAdapter.java
new file mode 100644
index 0000000..a7fba29
--- /dev/null
+++ b/lifecycle/compiler/src/tests/test-data/expected/InterfaceOk2Interface_LifecycleAdapter.java
@@ -0,0 +1,42 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package foo;
+
+import android.arch.lifecycle.GenericLifecycleObserver;
+import android.arch.lifecycle.Lifecycle;
+import android.arch.lifecycle.LifecycleOwner;
+import java.lang.Object;
+import java.lang.Override;
+
+public class InterfaceOk2Interface_LifecycleAdapter implements GenericLifecycleObserver {
+  final InterfaceOk2Interface mReceiver;
+
+  InterfaceOk2Interface_LifecycleAdapter(InterfaceOk2Interface receiver) {
+    this.mReceiver = receiver;
+  }
+
+  @Override
+  public void onStateChanged(LifecycleOwner owner, Lifecycle.Event event) {
+    if (event == Lifecycle.Event.ON_STOP) {
+      mReceiver.onStop2(owner);
+    }
+  }
+
+  public Object getReceiver() {
+    return mReceiver;
+  }
+}
diff --git a/lifecycle/compiler/src/tests/test-data/expected/OnAnyMethod_LifecycleAdapter.java b/lifecycle/compiler/src/tests/test-data/expected/OnAnyMethod_LifecycleAdapter.java
new file mode 100644
index 0000000..dc59207
--- /dev/null
+++ b/lifecycle/compiler/src/tests/test-data/expected/OnAnyMethod_LifecycleAdapter.java
@@ -0,0 +1,44 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package foo;
+
+import android.arch.lifecycle.GenericLifecycleObserver;
+import android.arch.lifecycle.Lifecycle;
+import android.arch.lifecycle.LifecycleOwner;
+import java.lang.Object;
+import java.lang.Override;
+
+public class OnAnyMethod_LifecycleAdapter implements GenericLifecycleObserver {
+  final OnAnyMethod mReceiver;
+
+  OnAnyMethod_LifecycleAdapter(OnAnyMethod receiver) {
+    this.mReceiver = receiver;
+  }
+
+  @Override
+  public void onStateChanged(LifecycleOwner owner, Lifecycle.Event event) {
+    if (event == Lifecycle.Event.ON_STOP) {
+      mReceiver.onStop(owner);
+    }
+    mReceiver.any(owner);
+    mReceiver.any(owner,event);
+  }
+
+  public Object getReceiver() {
+    return mReceiver;
+  }
+}
diff --git a/lifecycle/compiler/src/tests/test-data/expected/license.txt b/lifecycle/compiler/src/tests/test-data/expected/license.txt
new file mode 100644
index 0000000..a538eb5
--- /dev/null
+++ b/lifecycle/compiler/src/tests/test-data/expected/license.txt
@@ -0,0 +1,15 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
diff --git a/lifecycle/extensions/.gitignore b/lifecycle/extensions/.gitignore
new file mode 100644
index 0000000..796b96d
--- /dev/null
+++ b/lifecycle/extensions/.gitignore
@@ -0,0 +1 @@
+/build
diff --git a/lifecycle/extensions/build.gradle b/lifecycle/extensions/build.gradle
new file mode 100644
index 0000000..daef74b
--- /dev/null
+++ b/lifecycle/extensions/build.gradle
@@ -0,0 +1,57 @@
+apply plugin: 'com.android.library'
+apply plugin: 'maven'
+
+android {
+    compileSdkVersion tools.current_sdk
+    buildToolsVersion tools.build_tools_version
+
+    defaultConfig {
+        minSdkVersion flatfoot.min_sdk
+        targetSdkVersion tools.current_sdk
+        versionCode 1
+        versionName "1.0"
+
+        testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
+
+    }
+
+    buildTypes.all {
+        consumerProguardFiles 'proguard-rules.pro'
+    }
+
+    buildTypes {
+        release {
+            minifyEnabled false
+        }
+    }
+
+    testOptions {
+        unitTests.returnDefaultValues = true
+    }
+}
+dependencies {
+    compile project(":lifecycle:common")
+    compile project(":lifecycle:runtime")
+    compile project(":arch:runtime")
+    compile libs.support.fragments
+
+    testCompile project(":arch:core-testing")
+    testCompile libs.junit
+    testCompile libs.mockito_core
+
+    testCompile(libs.test_runner) {
+        exclude module: 'support-annotations'
+    }
+    androidTestCompile(libs.test_runner) {
+        exclude module: 'support-annotations'
+    }
+    androidTestCompile libs.support.app_compat
+    androidTestCompile(libs.espresso_core, {
+        exclude group: 'com.android.support', module: 'support-annotations'
+    })
+}
+
+createAndroidCheckstyle(project)
+
+//noinspection GroovyUnusedAssignment
+archivesBaseName = "extensions"
diff --git a/lifecycle/extensions/proguard-rules.pro b/lifecycle/extensions/proguard-rules.pro
new file mode 100644
index 0000000..a24a5d3
--- /dev/null
+++ b/lifecycle/extensions/proguard-rules.pro
@@ -0,0 +1,7 @@
+-keep class * extends android.arch.lifecycle.ViewModel {
+    <init>();
+}
+
+-keep class * extends android.arch.lifecycle.AndroidViewModel {
+    <init>(android.app.Application);
+}
\ No newline at end of file
diff --git a/lifecycle/extensions/src/androidTest/AndroidManifest.xml b/lifecycle/extensions/src/androidTest/AndroidManifest.xml
new file mode 100644
index 0000000..08e1de6
--- /dev/null
+++ b/lifecycle/extensions/src/androidTest/AndroidManifest.xml
@@ -0,0 +1,32 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  ~ Copyright (C) 2016 The Android Open Source Project
+  ~
+  ~ Licensed under the Apache License, Version 2.0 (the "License");
+  ~ you may not use this file except in compliance with the License.
+  ~ You may obtain a copy of the License at
+  ~
+  ~      http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing, software
+  ~ distributed under the License is distributed on an "AS IS" BASIS,
+  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  ~ See the License for the specific language governing permissions and
+  ~ limitations under the License.
+  -->
+
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+          package="android.arch.lifecycle.extensions.test">
+
+    <application>
+        <activity android:name="android.arch.lifecycle.viewmodeltest.ViewModelActivity"
+                  android:theme="@style/Base.Theme.AppCompat">
+        </activity>
+        <activity android:name="android.arch.lifecycle.EmptyActivity"/>
+        <activity android:name="android.arch.lifecycle.activity.FragmentLifecycleActivity"
+            android:theme="@style/Base.Theme.AppCompat"/>
+        <activity android:name="android.arch.lifecycle.activity.EmptyActivity"/>
+        <service android:name="android.arch.lifecycle.service.TestService"/>
+    </application>
+
+</manifest>
diff --git a/lifecycle/extensions/src/androidTest/java/android/arch/lifecycle/EmptyActivity.java b/lifecycle/extensions/src/androidTest/java/android/arch/lifecycle/EmptyActivity.java
new file mode 100644
index 0000000..738bd66
--- /dev/null
+++ b/lifecycle/extensions/src/androidTest/java/android/arch/lifecycle/EmptyActivity.java
@@ -0,0 +1,22 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.arch.lifecycle;
+
+import android.support.v4.app.FragmentActivity;
+
+public class EmptyActivity extends FragmentActivity {
+}
diff --git a/lifecycle/extensions/src/androidTest/java/android/arch/lifecycle/FragmentLifecycleInActivityTest.java b/lifecycle/extensions/src/androidTest/java/android/arch/lifecycle/FragmentLifecycleInActivityTest.java
new file mode 100644
index 0000000..70a4465
--- /dev/null
+++ b/lifecycle/extensions/src/androidTest/java/android/arch/lifecycle/FragmentLifecycleInActivityTest.java
@@ -0,0 +1,155 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.arch.lifecycle;
+
+import static android.arch.lifecycle.Lifecycle.Event.ON_CREATE;
+import static android.arch.lifecycle.Lifecycle.Event.ON_DESTROY;
+import static android.arch.lifecycle.Lifecycle.Event.ON_PAUSE;
+import static android.arch.lifecycle.Lifecycle.Event.ON_RESUME;
+import static android.arch.lifecycle.Lifecycle.Event.ON_START;
+import static android.arch.lifecycle.Lifecycle.Event.ON_STOP;
+
+import static org.hamcrest.CoreMatchers.instanceOf;
+import static org.hamcrest.CoreMatchers.is;
+import static org.hamcrest.CoreMatchers.notNullValue;
+import static org.hamcrest.MatcherAssert.assertThat;
+
+import android.app.Instrumentation;
+import android.arch.lifecycle.activity.FragmentLifecycleActivity;
+import android.content.Intent;
+import android.support.test.InstrumentationRegistry;
+import android.support.test.filters.MediumTest;
+import android.support.test.rule.ActivityTestRule;
+import android.support.v4.app.Fragment;
+
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+
+import java.util.Arrays;
+import java.util.concurrent.TimeUnit;
+
+@MediumTest
+@RunWith(Parameterized.class)
+public class FragmentLifecycleInActivityTest {
+
+    private static final long TIMEOUT = 2; //sec
+
+    @Rule
+    public ActivityTestRule<FragmentLifecycleActivity> mActivityRule =
+            new ActivityTestRule<>(FragmentLifecycleActivity.class, false, false);
+
+    private Instrumentation mInstrumentation;
+
+    @SuppressWarnings("WeakerAccess")
+    @Parameterized.Parameter
+    public boolean mNested;
+
+    @Parameterized.Parameters(name = "nested_{0}")
+    public static Object[][] params() {
+        return new Object[][]{new Object[]{false}, new Object[]{true}};
+    }
+
+    @Before
+    public void getInstrumentation() {
+        mInstrumentation = InstrumentationRegistry.getInstrumentation();
+    }
+
+    private void reset() {
+        mActivityRule.getActivity().resetEvents();
+    }
+
+    @Test
+    public void testFullEvents() throws Throwable {
+        final FragmentLifecycleActivity activity = launchActivity();
+        waitForIdle();
+        assertEvents(ON_CREATE, ON_START, ON_RESUME);
+        reset();
+        finishActivity(activity);
+        assertEvents(ON_PAUSE, ON_STOP, ON_DESTROY);
+    }
+
+    @Test
+    public void testStopStart() throws Throwable {
+        final FragmentLifecycleActivity activity = launchActivity();
+        waitForIdle();
+        assertEvents(ON_CREATE, ON_START, ON_RESUME);
+        reset();
+        mActivityRule.runOnUiThread(new Runnable() {
+            @Override
+            public void run() {
+                mInstrumentation.callActivityOnPause(activity);
+                mInstrumentation.callActivityOnStop(activity);
+            }
+        });
+        waitForIdle();
+        assertEvents(ON_PAUSE, ON_STOP);
+        reset();
+        mActivityRule.runOnUiThread(new Runnable() {
+            @Override
+            public void run() {
+                mInstrumentation.callActivityOnStart(activity);
+                mInstrumentation.callActivityOnResume(activity);
+            }
+        });
+        waitForIdle();
+        assertEvents(ON_START, ON_RESUME);
+    }
+
+    private FragmentLifecycleActivity launchActivity() throws Throwable {
+        Intent intent = FragmentLifecycleActivity.intentFor(mInstrumentation.getTargetContext(),
+                mNested);
+        final FragmentLifecycleActivity activity = mActivityRule.launchActivity(intent);
+        mActivityRule.runOnUiThread(new Runnable() {
+            @Override
+            public void run() {
+                Fragment main = activity.getSupportFragmentManager()
+                        .findFragmentByTag(FragmentLifecycleActivity.MAIN_TAG);
+                assertThat("test sanity", main, notNullValue());
+                Fragment nestedFragment = main.getChildFragmentManager()
+                        .findFragmentByTag(FragmentLifecycleActivity.NESTED_TAG);
+                assertThat("test sanity", nestedFragment != null, is(mNested));
+            }
+        });
+        assertThat(activity.getObservedOwner(), instanceOf(
+                mNested ? FragmentLifecycleActivity.NestedFragment.class
+                        : FragmentLifecycleActivity.MainFragment.class
+        ));
+        return activity;
+    }
+
+    private void waitForIdle() {
+        mInstrumentation.waitForIdleSync();
+    }
+
+    private void finishActivity(final FragmentLifecycleActivity activity)
+            throws InterruptedException {
+        mInstrumentation.runOnMainSync(new Runnable() {
+            @Override
+            public void run() {
+                activity.finish();
+            }
+        });
+        assertThat(activity.awaitForDestruction(TIMEOUT, TimeUnit.SECONDS), is(true));
+    }
+
+    private void assertEvents(Lifecycle.Event... events) {
+        assertThat(mActivityRule.getActivity().getLoggedEvents(), is(Arrays.asList(events)));
+    }
+}
diff --git a/lifecycle/extensions/src/androidTest/java/android/arch/lifecycle/FragmentOperationsLifecycleTest.java b/lifecycle/extensions/src/androidTest/java/android/arch/lifecycle/FragmentOperationsLifecycleTest.java
new file mode 100644
index 0000000..be062cb
--- /dev/null
+++ b/lifecycle/extensions/src/androidTest/java/android/arch/lifecycle/FragmentOperationsLifecycleTest.java
@@ -0,0 +1,118 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.arch.lifecycle;
+
+import static android.arch.lifecycle.Lifecycle.Event.ON_CREATE;
+import static android.arch.lifecycle.Lifecycle.Event.ON_DESTROY;
+import static android.arch.lifecycle.Lifecycle.Event.ON_PAUSE;
+import static android.arch.lifecycle.Lifecycle.Event.ON_RESUME;
+import static android.arch.lifecycle.Lifecycle.Event.ON_START;
+import static android.arch.lifecycle.Lifecycle.Event.ON_STOP;
+
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.hamcrest.Matchers.is;
+
+import static java.util.Arrays.asList;
+
+import android.arch.lifecycle.activity.EmptyActivity;
+import android.arch.lifecycle.extensions.test.R;
+import android.support.test.annotation.UiThreadTest;
+import android.support.test.filters.MediumTest;
+import android.support.test.rule.ActivityTestRule;
+import android.support.test.runner.AndroidJUnit4;
+import android.support.v4.app.FragmentManager;
+
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.util.ArrayList;
+import java.util.List;
+
+@MediumTest
+@RunWith(AndroidJUnit4.class)
+public class FragmentOperationsLifecycleTest {
+
+    @Rule
+    public ActivityTestRule<EmptyActivity> mActivityTestRule = new ActivityTestRule<>(
+            EmptyActivity.class);
+
+    @Test
+    @UiThreadTest
+    public void addRemoveFragment() {
+        EmptyActivity activity = mActivityTestRule.getActivity();
+        LifecycleFragment fragment = new LifecycleFragment();
+        FragmentManager fm = activity.getSupportFragmentManager();
+        fm.beginTransaction().add(fragment, "tag").commitNow();
+        CollectingObserver observer = observeAndCollectIn(fragment);
+        assertThat(observer.getEventsAndReset(), is(asList(ON_CREATE, ON_START, ON_RESUME)));
+        fm.beginTransaction().remove(fragment).commitNow();
+        assertThat(observer.getEventsAndReset(), is(asList(ON_PAUSE, ON_STOP, ON_DESTROY)));
+        fm.beginTransaction().add(fragment, "tag").commitNow();
+        assertThat(observer.getEventsAndReset(), is(asList(ON_CREATE, ON_START, ON_RESUME)));
+    }
+
+    @Test
+    @UiThreadTest
+    public void fragmentInBackstack() {
+        EmptyActivity activity = mActivityTestRule.getActivity();
+        LifecycleFragment fragment1 = new LifecycleFragment();
+        FragmentManager fm = activity.getSupportFragmentManager();
+        fm.beginTransaction().add(R.id.fragment_container, fragment1, "tag").addToBackStack(null)
+                .commit();
+        fm.executePendingTransactions();
+        CollectingObserver observer1 = observeAndCollectIn(fragment1);
+        assertThat(observer1.getEventsAndReset(), is(asList(ON_CREATE, ON_START, ON_RESUME)));
+
+        LifecycleFragment fragment2 = new LifecycleFragment();
+        fm.beginTransaction().replace(R.id.fragment_container, fragment2).addToBackStack(null)
+                .commit();
+        fm.executePendingTransactions();
+
+        CollectingObserver observer2 = observeAndCollectIn(fragment2);
+        assertThat(observer1.getEventsAndReset(), is(asList(ON_PAUSE, ON_STOP)));
+        assertThat(observer2.getEventsAndReset(), is(asList(ON_CREATE, ON_START, ON_RESUME)));
+
+        assertThat(fm.popBackStackImmediate(), is(true));
+        assertThat(observer1.getEventsAndReset(), is(asList(ON_START, ON_RESUME)));
+        assertThat(observer2.getEventsAndReset(), is(asList(ON_PAUSE, ON_STOP, ON_DESTROY)));
+
+        assertThat(fm.popBackStackImmediate(), is(true));
+        assertThat(observer1.getEventsAndReset(), is(asList(ON_PAUSE, ON_STOP, ON_DESTROY)));
+    }
+
+    private static CollectingObserver observeAndCollectIn(LifecycleFragment fragment) {
+        CollectingObserver observer = new CollectingObserver();
+        fragment.getLifecycle().addObserver(observer);
+        return observer;
+    }
+
+    private static class CollectingObserver implements LifecycleObserver {
+        final List<Lifecycle.Event> mCollectedEvents = new ArrayList<>();
+
+        @OnLifecycleEvent(Lifecycle.Event.ON_ANY)
+        public void anyEvent(LifecycleOwner owner, Lifecycle.Event event) {
+            mCollectedEvents.add(event);
+        }
+
+        List<Lifecycle.Event> getEventsAndReset() {
+            ArrayList<Lifecycle.Event> events = new ArrayList<>(mCollectedEvents);
+            mCollectedEvents.clear();
+            return events;
+        }
+    }
+}
diff --git a/lifecycle/extensions/src/androidTest/java/android/arch/lifecycle/ServiceLifecycleTest.java b/lifecycle/extensions/src/androidTest/java/android/arch/lifecycle/ServiceLifecycleTest.java
new file mode 100644
index 0000000..fe0a306
--- /dev/null
+++ b/lifecycle/extensions/src/androidTest/java/android/arch/lifecycle/ServiceLifecycleTest.java
@@ -0,0 +1,249 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.arch.lifecycle;
+
+import static android.arch.lifecycle.Lifecycle.Event.ON_CREATE;
+import static android.arch.lifecycle.Lifecycle.Event.ON_DESTROY;
+import static android.arch.lifecycle.Lifecycle.Event.ON_START;
+import static android.arch.lifecycle.Lifecycle.Event.ON_STOP;
+
+import static org.hamcrest.CoreMatchers.is;
+import static org.hamcrest.MatcherAssert.assertThat;
+
+import android.arch.lifecycle.Lifecycle.Event;
+import android.arch.lifecycle.service.TestService;
+import android.content.BroadcastReceiver;
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.content.ServiceConnection;
+import android.os.IBinder;
+import android.support.test.InstrumentationRegistry;
+import android.support.test.filters.MediumTest;
+import android.support.test.runner.AndroidJUnit4;
+import android.support.v4.content.LocalBroadcastManager;
+
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.TimeoutException;
+
+@MediumTest
+@RunWith(AndroidJUnit4.class)
+public class ServiceLifecycleTest {
+
+    private static final int RETRY_NUMBER = 5;
+    private static final long TIMEOUT = TimeUnit.SECONDS.toMillis(1);
+
+    private Intent mServiceIntent;
+
+    private volatile List<Event> mLoggerEvents;
+    private EventLogger mLogger;
+
+    @Before
+    public void setUp() {
+        Context context = InstrumentationRegistry.getTargetContext();
+        mServiceIntent = new Intent(context, TestService.class);
+        LocalBroadcastManager localBroadcastManager = LocalBroadcastManager.getInstance(context);
+        IntentFilter intentFilter = new IntentFilter();
+        intentFilter.addAction(TestService.ACTION_LOG_EVENT);
+
+        // Overcautiousness: each EventLogger has its own events list, so one bad test won't spoil
+        // others.
+        mLoggerEvents = new ArrayList<>();
+        mLogger = new EventLogger(mLoggerEvents);
+        localBroadcastManager.registerReceiver(mLogger, intentFilter);
+
+    }
+
+    @After
+    public void tearDown() {
+        Context context = InstrumentationRegistry.getTargetContext();
+        LocalBroadcastManager localBroadcastManager = LocalBroadcastManager.getInstance(context);
+        localBroadcastManager.unregisterReceiver(mLogger);
+        mLogger = null;
+        mLoggerEvents = null;
+    }
+
+    @Test
+    public void testUnboundedService() throws TimeoutException, InterruptedException {
+        Context context = InstrumentationRegistry.getTargetContext();
+        context.startService(mServiceIntent);
+        awaitAndAssertEvents(ON_CREATE, ON_START);
+        context.stopService(mServiceIntent);
+        awaitAndAssertEvents(ON_CREATE, ON_START, ON_STOP, ON_DESTROY);
+    }
+
+    @Test
+    public void testBoundedService() throws TimeoutException, InterruptedException {
+        ServiceConnection connection = bindToService();
+        awaitAndAssertEvents(ON_CREATE, ON_START);
+        InstrumentationRegistry.getTargetContext().unbindService(connection);
+        awaitAndAssertEvents(ON_CREATE, ON_START, ON_STOP, ON_DESTROY);
+    }
+
+    @Test
+    public void testStartBindUnbindStop() throws InterruptedException {
+        Context context = InstrumentationRegistry.getTargetContext();
+        context.startService(mServiceIntent);
+        awaitAndAssertEvents(ON_CREATE, ON_START);
+
+        ServiceConnection connection = bindToService();
+        // Precaution: give a chance to dispatch events
+        InstrumentationRegistry.getInstrumentation().waitForIdleSync();
+        // still the same events
+        awaitAndAssertEvents(ON_CREATE, ON_START);
+
+        context.unbindService(connection);
+        // Precaution: give a chance to dispatch events
+        InstrumentationRegistry.getInstrumentation().waitForIdleSync();
+        // service is still started (stopServices/stopSelf weren't called)
+        awaitAndAssertEvents(ON_CREATE, ON_START);
+
+        context.stopService(mServiceIntent);
+        awaitAndAssertEvents(ON_CREATE, ON_START, ON_STOP, ON_DESTROY);
+    }
+
+    @Test
+    public void testStartBindStopUnbind() throws InterruptedException {
+        Context context = InstrumentationRegistry.getTargetContext();
+        context.startService(mServiceIntent);
+        awaitAndAssertEvents(ON_CREATE, ON_START);
+
+        ServiceConnection connection = bindToService();
+        // Precaution: give a chance to dispatch events
+        InstrumentationRegistry.getInstrumentation().waitForIdleSync();
+        // still the same events
+        awaitAndAssertEvents(ON_CREATE, ON_START);
+
+        context.stopService(mServiceIntent);
+        // Precaution: give a chance to dispatch events
+        InstrumentationRegistry.getInstrumentation().waitForIdleSync();
+        // service is still bound
+        awaitAndAssertEvents(ON_CREATE, ON_START);
+
+        context.unbindService(connection);
+        awaitAndAssertEvents(ON_CREATE, ON_START,
+                ON_STOP, ON_DESTROY);
+    }
+
+    @Test
+    public void testBindStartUnbindStop() throws InterruptedException {
+        Context context = InstrumentationRegistry.getTargetContext();
+        ServiceConnection connection = bindToService();
+        awaitAndAssertEvents(ON_CREATE, ON_START);
+
+
+        context.startService(mServiceIntent);
+        // Precaution: give a chance to dispatch events
+        InstrumentationRegistry.getInstrumentation().waitForIdleSync();
+        // still the same events
+        awaitAndAssertEvents(ON_CREATE, ON_START);
+
+        context.unbindService(connection);
+        // Precaution: give a chance to dispatch events
+        InstrumentationRegistry.getInstrumentation().waitForIdleSync();
+        // service is still started (stopServices/stopSelf weren't called)
+        awaitAndAssertEvents(ON_CREATE, ON_START);
+
+        context.stopService(mServiceIntent);
+        awaitAndAssertEvents(ON_CREATE, ON_START,
+                ON_STOP, ON_DESTROY);
+    }
+
+    @Test
+    public void testBindStartStopUnbind() throws InterruptedException {
+        Context context = InstrumentationRegistry.getTargetContext();
+        ServiceConnection connection = bindToService();
+        awaitAndAssertEvents(ON_CREATE, ON_START);
+
+        context.startService(mServiceIntent);
+        // Precaution: give a chance to dispatch events
+        InstrumentationRegistry.getInstrumentation().waitForIdleSync();
+        // still the same events
+        awaitAndAssertEvents(ON_CREATE, ON_START);
+
+        context.stopService(mServiceIntent);
+        // Precaution: give a chance to dispatch events
+        InstrumentationRegistry.getInstrumentation().waitForIdleSync();
+        // service is still bound
+        awaitAndAssertEvents(ON_CREATE, ON_START);
+
+        context.unbindService(connection);
+        awaitAndAssertEvents(ON_CREATE, ON_START,
+                ON_STOP, ON_DESTROY);
+    }
+
+    // can't use ServiceTestRule because it proxies connection, so we can't use unbindService method
+    private ServiceConnection bindToService() throws InterruptedException {
+        Context context = InstrumentationRegistry.getTargetContext();
+        final CountDownLatch latch = new CountDownLatch(1);
+        ServiceConnection connection = new ServiceConnection() {
+            @Override
+            public void onServiceConnected(ComponentName name, IBinder service) {
+                latch.countDown();
+            }
+
+            @Override
+            public void onServiceDisconnected(ComponentName name) {
+
+            }
+        };
+
+        boolean success = context.bindService(mServiceIntent, connection, Context.BIND_AUTO_CREATE);
+        assertThat(success, is(true));
+        boolean awaited = latch.await(TIMEOUT, TimeUnit.MILLISECONDS);
+        assertThat(awaited, is(true));
+        return connection;
+    }
+
+    private void awaitAndAssertEvents(Event... events) throws InterruptedException {
+        //noinspection SynchronizeOnNonFinalField
+        synchronized (mLoggerEvents) {
+            int retryCount = 0;
+            while (mLoggerEvents.size() < events.length && retryCount++ < RETRY_NUMBER) {
+                mLoggerEvents.wait(TIMEOUT);
+            }
+            assertThat(mLoggerEvents, is(Arrays.asList(events)));
+        }
+    }
+
+    private static class EventLogger extends BroadcastReceiver {
+        private final List<Event> mLoggerEvents;
+
+        private EventLogger(List<Event> loggerEvents) {
+            mLoggerEvents = loggerEvents;
+        }
+
+        @Override
+        public void onReceive(Context context, Intent intent) {
+            synchronized (mLoggerEvents) {
+                mLoggerEvents.add((Event) intent.getSerializableExtra(TestService.EXTRA_KEY_EVENT));
+                mLoggerEvents.notifyAll();
+            }
+        }
+    }
+}
diff --git a/lifecycle/extensions/src/androidTest/java/android/arch/lifecycle/ViewModelTest.java b/lifecycle/extensions/src/androidTest/java/android/arch/lifecycle/ViewModelTest.java
new file mode 100644
index 0000000..98ce027
--- /dev/null
+++ b/lifecycle/extensions/src/androidTest/java/android/arch/lifecycle/ViewModelTest.java
@@ -0,0 +1,191 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.arch.lifecycle;
+
+import static android.arch.lifecycle.Lifecycle.Event.ON_RESUME;
+
+import static org.hamcrest.CoreMatchers.is;
+import static org.hamcrest.CoreMatchers.not;
+import static org.hamcrest.CoreMatchers.notNullValue;
+import static org.hamcrest.MatcherAssert.assertThat;
+
+import android.app.Instrumentation;
+import android.arch.lifecycle.viewmodeltest.TestViewModel;
+import android.arch.lifecycle.viewmodeltest.ViewModelActivity;
+import android.arch.lifecycle.viewmodeltest.ViewModelActivity.ViewModelFragment;
+import android.support.test.InstrumentationRegistry;
+import android.support.test.annotation.UiThreadTest;
+import android.support.test.filters.MediumTest;
+import android.support.test.rule.ActivityTestRule;
+import android.support.test.runner.AndroidJUnit4;
+import android.support.v4.app.FragmentActivity;
+import android.support.v4.app.FragmentManager;
+
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.TimeUnit;
+
+@MediumTest
+@RunWith(AndroidJUnit4.class)
+public class ViewModelTest {
+    private static final int TIMEOUT = 2; // secs
+
+    @Rule
+    public ActivityTestRule<ViewModelActivity> mActivityRule =
+            new ActivityTestRule<>(ViewModelActivity.class);
+
+    @Test
+    public void ensureSameViewHolders() throws Throwable {
+        final TestViewModel[] activityModel = new TestViewModel[1];
+        final TestViewModel[] defaultActivityModel = new TestViewModel[1];
+        final TestViewModel[] fragment1Model = new TestViewModel[1];
+        final TestViewModel[] fragment2Model = new TestViewModel[1];
+        final ViewModelActivity[] viewModelActivity = new ViewModelActivity[1];
+        viewModelActivity[0] = mActivityRule.getActivity();
+        mActivityRule.runOnUiThread(new Runnable() {
+            @Override
+            public void run() {
+                ViewModelFragment fragment1 = getFragment(viewModelActivity[0],
+                        ViewModelActivity.FRAGMENT_TAG_1);
+                ViewModelFragment fragment2 = getFragment(viewModelActivity[0],
+                        ViewModelActivity.FRAGMENT_TAG_2);
+                assertThat(fragment1, notNullValue());
+                assertThat(fragment2, notNullValue());
+                assertThat(fragment1.activityModel, is(fragment2.activityModel));
+                assertThat(fragment1.fragmentModel, not(is(fragment2.activityModel)));
+                assertThat(mActivityRule.getActivity().activityModel, is(fragment1.activityModel));
+                activityModel[0] = mActivityRule.getActivity().activityModel;
+                defaultActivityModel[0] = mActivityRule.getActivity().defaultActivityModel;
+                assertThat(defaultActivityModel[0], not(is(activityModel[0])));
+                fragment1Model[0] = fragment1.fragmentModel;
+                fragment2Model[0] = fragment2.fragmentModel;
+            }
+        });
+        viewModelActivity[0] = recreateActivity();
+        mActivityRule.runOnUiThread(new Runnable() {
+            @Override
+            public void run() {
+                ViewModelFragment fragment1 = getFragment(viewModelActivity[0],
+                        ViewModelActivity.FRAGMENT_TAG_1);
+                ViewModelFragment fragment2 = getFragment(viewModelActivity[0],
+                        ViewModelActivity.FRAGMENT_TAG_2);
+                assertThat(fragment1, notNullValue());
+                assertThat(fragment2, notNullValue());
+
+                assertThat(fragment1.activityModel, is(activityModel[0]));
+                assertThat(fragment2.activityModel, is(activityModel[0]));
+                assertThat(fragment1.fragmentModel, is(fragment1Model[0]));
+                assertThat(fragment2.fragmentModel, is(fragment2Model[0]));
+                assertThat(fragment1.defaultActivityModel, is(defaultActivityModel[0]));
+                assertThat(fragment2.defaultActivityModel, is(defaultActivityModel[0]));
+                assertThat(mActivityRule.getActivity().activityModel, is(activityModel[0]));
+                assertThat(mActivityRule.getActivity().defaultActivityModel,
+                        is(defaultActivityModel[0]));
+            }
+        });
+    }
+
+    @Test
+    @UiThreadTest
+    public void testGetApplication() {
+        TestViewModel activityModel = mActivityRule.getActivity().activityModel;
+        assertThat(activityModel.getApplication(),
+                is(InstrumentationRegistry.getTargetContext().getApplicationContext()));
+    }
+
+    @Test
+    public void testOnClear() throws Throwable {
+        final ViewModelActivity activity = mActivityRule.getActivity();
+        final CountDownLatch latch = new CountDownLatch(1);
+        final LifecycleObserver observer = new LifecycleObserver() {
+            @SuppressWarnings("unused")
+            @OnLifecycleEvent(ON_RESUME)
+            void onResume() {
+                try {
+                    final FragmentManager manager = activity.getSupportFragmentManager();
+                    LifecycleFragment fragment = new LifecycleFragment();
+                    manager.beginTransaction().add(fragment, "temp").commitNow();
+                    ViewModel1 vm = ViewModelProviders.of(fragment).get(ViewModel1.class);
+                    assertThat(vm.mCleared, is(false));
+                    manager.beginTransaction().remove(fragment).commitNow();
+                    assertThat(vm.mCleared, is(true));
+                } finally {
+                    latch.countDown();
+                }
+            }
+        };
+
+        mActivityRule.runOnUiThread(new Runnable() {
+            @Override
+            public void run() {
+                activity.getLifecycle().addObserver(observer);
+            }
+        });
+        assertThat(latch.await(TIMEOUT, TimeUnit.SECONDS), is(true));
+    }
+
+    private ViewModelFragment getFragment(FragmentActivity activity, String tag) {
+        return (ViewModelFragment) activity.getSupportFragmentManager()
+                .findFragmentByTag(tag);
+    }
+
+    private ViewModelActivity recreateActivity() throws Throwable {
+        Instrumentation.ActivityMonitor monitor = new Instrumentation.ActivityMonitor(
+                ViewModelActivity.class.getCanonicalName(), null, false);
+        Instrumentation instrumentation = InstrumentationRegistry.getInstrumentation();
+        instrumentation.addMonitor(monitor);
+        final ViewModelActivity previous = mActivityRule.getActivity();
+        mActivityRule.runOnUiThread(new Runnable() {
+            @Override
+            public void run() {
+                previous.recreate();
+            }
+        });
+        ViewModelActivity result;
+
+        // this guarantee that we will reinstall monitor between notifications about onDestroy
+        // and onCreate
+        //noinspection SynchronizationOnLocalVariableOrMethodParameter
+        synchronized (monitor) {
+            do {
+                // the documentation says "Block until an Activity is created
+                // that matches this monitor." This statement is true, but there are some other
+                // true statements like: "Block until an Activity is destroyed" or
+                // "Block until an Activity is resumed"...
+
+                // this call will release synchronization monitor's monitor
+                result = (ViewModelActivity) monitor.waitForActivityWithTimeout(4000);
+                if (result == null) {
+                    throw new RuntimeException("Timeout. Failed to recreate an activity");
+                }
+            } while (result == previous);
+        }
+        return result;
+    }
+
+    public static class ViewModel1 extends ViewModel {
+        boolean mCleared = false;
+
+        @Override
+        protected void onCleared() {
+            mCleared = true;
+        }
+    }
+}
diff --git a/lifecycle/extensions/src/androidTest/java/android/arch/lifecycle/ViewModelTestInTransaction.java b/lifecycle/extensions/src/androidTest/java/android/arch/lifecycle/ViewModelTestInTransaction.java
new file mode 100644
index 0000000..3832b3b
--- /dev/null
+++ b/lifecycle/extensions/src/androidTest/java/android/arch/lifecycle/ViewModelTestInTransaction.java
@@ -0,0 +1,94 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.arch.lifecycle;
+
+import static org.hamcrest.CoreMatchers.is;
+import static org.hamcrest.CoreMatchers.notNullValue;
+import static org.hamcrest.MatcherAssert.assertThat;
+
+import android.os.Bundle;
+import android.support.annotation.Nullable;
+import android.support.test.annotation.UiThreadTest;
+import android.support.test.filters.MediumTest;
+import android.support.test.rule.ActivityTestRule;
+import android.support.test.runner.AndroidJUnit4;
+import android.support.v4.app.Fragment;
+
+import android.arch.lifecycle.viewmodeltest.TestViewModel;
+
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+@MediumTest
+@RunWith(AndroidJUnit4.class)
+public class ViewModelTestInTransaction {
+
+    @Rule
+    public ActivityTestRule<EmptyActivity> mActivityRule =
+            new ActivityTestRule<>(EmptyActivity.class);
+
+    @Test
+    @UiThreadTest
+    public void testViewModelInTransactionActivity() {
+        EmptyActivity activity = mActivityRule.getActivity();
+        TestFragment fragment = new TestFragment();
+        activity.getSupportFragmentManager().beginTransaction().add(fragment, "tag").commitNow();
+        TestViewModel viewModel = ViewModelProviders.of(activity).get(TestViewModel.class);
+        assertThat(viewModel, is(fragment.mViewModel));
+    }
+
+    @Test
+    @UiThreadTest
+    public void testViewModelInTransactionFragment() {
+        EmptyActivity activity = mActivityRule.getActivity();
+        ParentFragment parent = new ParentFragment();
+        activity.getSupportFragmentManager().beginTransaction().add(parent, "parent").commitNow();
+        assertThat(parent.mExecuted, is(true));
+    }
+
+
+    public static class ParentFragment extends Fragment {
+
+        private boolean mExecuted;
+
+        @Override
+        public void onCreate(@Nullable Bundle savedInstanceState) {
+            super.onCreate(savedInstanceState);
+            TestFragment fragment = new TestFragment();
+            getChildFragmentManager().beginTransaction().add(fragment, "tag").commitNow();
+            TestViewModel viewModel = ViewModelProviders.of(this).get(TestViewModel.class);
+            assertThat(viewModel, is(fragment.mViewModel));
+            mExecuted = true;
+        }
+    }
+
+    public static class TestFragment extends Fragment {
+
+        TestViewModel mViewModel;
+
+        @Override
+        public void onCreate(@Nullable Bundle savedInstanceState) {
+            super.onCreate(savedInstanceState);
+            Fragment parentFragment = getParentFragment();
+            ViewModelProvider provider = parentFragment != null
+                    ? ViewModelProviders.of(parentFragment) : ViewModelProviders.of(getActivity());
+            mViewModel = provider.get(TestViewModel.class);
+            assertThat(mViewModel, notNullValue());
+        }
+    }
+}
diff --git a/lifecycle/extensions/src/androidTest/java/android/arch/lifecycle/activity/EmptyActivity.java b/lifecycle/extensions/src/androidTest/java/android/arch/lifecycle/activity/EmptyActivity.java
new file mode 100644
index 0000000..017fff4
--- /dev/null
+++ b/lifecycle/extensions/src/androidTest/java/android/arch/lifecycle/activity/EmptyActivity.java
@@ -0,0 +1,31 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.arch.lifecycle.activity;
+
+import android.os.Bundle;
+import android.support.annotation.Nullable;
+
+import android.arch.lifecycle.LifecycleActivity;
+import android.arch.lifecycle.extensions.test.R;
+
+public class EmptyActivity extends LifecycleActivity {
+    @Override
+    protected void onCreate(@Nullable Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+        setContentView(R.layout.activity_main);
+    }
+}
diff --git a/lifecycle/extensions/src/androidTest/java/android/arch/lifecycle/activity/FragmentLifecycleActivity.java b/lifecycle/extensions/src/androidTest/java/android/arch/lifecycle/activity/FragmentLifecycleActivity.java
new file mode 100644
index 0000000..f4485e8
--- /dev/null
+++ b/lifecycle/extensions/src/androidTest/java/android/arch/lifecycle/activity/FragmentLifecycleActivity.java
@@ -0,0 +1,119 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.arch.lifecycle.activity;
+
+import android.arch.lifecycle.Lifecycle;
+import android.arch.lifecycle.LifecycleFragment;
+import android.arch.lifecycle.LifecycleObserver;
+import android.arch.lifecycle.LifecycleOwner;
+import android.arch.lifecycle.OnLifecycleEvent;
+import android.arch.lifecycle.extensions.test.R;
+import android.content.Context;
+import android.content.Intent;
+import android.os.Bundle;
+import android.support.annotation.Nullable;
+import android.support.v7.app.AppCompatActivity;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.TimeUnit;
+
+public class FragmentLifecycleActivity extends AppCompatActivity {
+    public static final String NESTED_TAG = "nested_fragment";
+    public static final String MAIN_TAG = "main_fragment";
+    private static final String EXTRA_NESTED = "nested";
+
+    private final List<Lifecycle.Event> mLoggedEvents = Collections
+            .synchronizedList(new ArrayList<Lifecycle.Event>());
+    private LifecycleOwner mObservedOwner;
+    private final CountDownLatch mDestroyLatch = new CountDownLatch(1);
+
+    @Override
+    protected void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+        setContentView(R.layout.activity_main);
+        MainFragment fragment;
+        fragment = new MainFragment();
+        boolean nested = getIntent().getBooleanExtra(EXTRA_NESTED, false);
+        if (nested) {
+            fragment.mNestedFragment = new NestedFragment();
+        }
+        observe(nested ? fragment.mNestedFragment : fragment);
+        getSupportFragmentManager().beginTransaction()
+                .add(R.id.fragment_container, fragment, MAIN_TAG)
+                .commit();
+    }
+
+    @Override
+    protected void onDestroy() {
+        super.onDestroy();
+        mDestroyLatch.countDown();
+    }
+
+    public void resetEvents() {
+        mLoggedEvents.clear();
+    }
+
+    public static class MainFragment extends LifecycleFragment {
+        @Nullable
+        LifecycleFragment mNestedFragment;
+
+        @Override
+        public void onCreate(@Nullable Bundle savedInstanceState) {
+            super.onCreate(savedInstanceState);
+            if (mNestedFragment != null) {
+                getChildFragmentManager().beginTransaction()
+                        .add(mNestedFragment, NESTED_TAG)
+                        .commit();
+            }
+        }
+    }
+
+    public static class NestedFragment extends LifecycleFragment {
+    }
+
+    public static Intent intentFor(Context context, boolean nested) {
+        Intent intent = new Intent(context, FragmentLifecycleActivity.class);
+        intent.putExtra(EXTRA_NESTED, nested);
+        return intent;
+    }
+
+    public void observe(LifecycleOwner provider) {
+        mObservedOwner = provider;
+        provider.getLifecycle().addObserver(new LifecycleObserver() {
+            @OnLifecycleEvent(Lifecycle.Event.ON_ANY)
+            public void anyEvent(LifecycleOwner owner, Lifecycle.Event event) {
+                mLoggedEvents.add(event);
+            }
+        });
+    }
+
+    public List<Lifecycle.Event> getLoggedEvents() {
+        return mLoggedEvents;
+    }
+
+    public LifecycleOwner getObservedOwner() {
+        return mObservedOwner;
+    }
+
+    public boolean awaitForDestruction(long timeout, TimeUnit timeUnit)
+            throws InterruptedException {
+        return mDestroyLatch.await(timeout, timeUnit);
+    }
+}
diff --git a/lifecycle/extensions/src/androidTest/java/android/arch/lifecycle/service/TestService.java b/lifecycle/extensions/src/androidTest/java/android/arch/lifecycle/service/TestService.java
new file mode 100644
index 0000000..fcf06cc
--- /dev/null
+++ b/lifecycle/extensions/src/androidTest/java/android/arch/lifecycle/service/TestService.java
@@ -0,0 +1,56 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.arch.lifecycle.service;
+
+import android.arch.lifecycle.Lifecycle;
+import android.arch.lifecycle.LifecycleObserver;
+import android.arch.lifecycle.LifecycleOwner;
+import android.arch.lifecycle.LifecycleService;
+import android.arch.lifecycle.OnLifecycleEvent;
+import android.content.Context;
+import android.content.Intent;
+import android.os.Binder;
+import android.os.IBinder;
+import android.support.annotation.Nullable;
+import android.support.v4.content.LocalBroadcastManager;
+
+public class TestService extends LifecycleService {
+
+    public static final String ACTION_LOG_EVENT = "ACTION_LOG_EVENT";
+    public static final String EXTRA_KEY_EVENT = "EXTRA_KEY_EVENT";
+
+    private final IBinder mBinder = new Binder();
+
+    public TestService() {
+        getLifecycle().addObserver(new LifecycleObserver() {
+            @OnLifecycleEvent(Lifecycle.Event.ON_ANY)
+            public void anyEvent(LifecycleOwner owner, Lifecycle.Event event) {
+                Context context = (TestService) owner;
+                Intent intent = new Intent(ACTION_LOG_EVENT);
+                intent.putExtra(EXTRA_KEY_EVENT, event);
+                LocalBroadcastManager.getInstance(context).sendBroadcast(intent);
+            }
+        });
+    }
+
+    @Nullable
+    @Override
+    public IBinder onBind(Intent intent) {
+        super.onBind(intent);
+        return mBinder;
+    }
+}
diff --git a/lifecycle/extensions/src/androidTest/java/android/arch/lifecycle/viewmodeltest/TestViewModel.java b/lifecycle/extensions/src/androidTest/java/android/arch/lifecycle/viewmodeltest/TestViewModel.java
new file mode 100644
index 0000000..ec3550c
--- /dev/null
+++ b/lifecycle/extensions/src/androidTest/java/android/arch/lifecycle/viewmodeltest/TestViewModel.java
@@ -0,0 +1,28 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.arch.lifecycle.viewmodeltest;
+
+import android.app.Application;
+
+import android.arch.lifecycle.AndroidViewModel;
+
+public class TestViewModel extends AndroidViewModel {
+
+    public TestViewModel(Application application) {
+        super(application);
+    }
+}
diff --git a/lifecycle/extensions/src/androidTest/java/android/arch/lifecycle/viewmodeltest/ViewModelActivity.java b/lifecycle/extensions/src/androidTest/java/android/arch/lifecycle/viewmodeltest/ViewModelActivity.java
new file mode 100644
index 0000000..5ef9f16
--- /dev/null
+++ b/lifecycle/extensions/src/androidTest/java/android/arch/lifecycle/viewmodeltest/ViewModelActivity.java
@@ -0,0 +1,65 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.arch.lifecycle.viewmodeltest;
+
+import android.arch.lifecycle.extensions.test.R;
+import android.os.Bundle;
+import android.support.annotation.Nullable;
+
+import android.arch.lifecycle.LifecycleActivity;
+import android.arch.lifecycle.LifecycleFragment;
+import android.arch.lifecycle.ViewModelProviders;
+
+public class ViewModelActivity extends LifecycleActivity {
+    public static final String KEY_FRAGMENT_MODEL = "fragment-model";
+    public static final String KEY_ACTIVITY_MODEL = "activity-model";
+    public static final String FRAGMENT_TAG_1 = "f1";
+    public static final String FRAGMENT_TAG_2 = "f2";
+
+    public TestViewModel activityModel;
+    public TestViewModel defaultActivityModel;
+
+    @Override
+    protected void onCreate(@Nullable Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+        setContentView(R.layout.activity_view_model);
+        if (savedInstanceState == null) {
+            getSupportFragmentManager().beginTransaction()
+                    .add(R.id.fragment_container, new ViewModelFragment(), FRAGMENT_TAG_1)
+                    .add(new ViewModelFragment(), FRAGMENT_TAG_2)
+                    .commit();
+        }
+        activityModel = ViewModelProviders.of(this).get(KEY_ACTIVITY_MODEL, TestViewModel.class);
+        defaultActivityModel = ViewModelProviders.of(this).get(TestViewModel.class);
+    }
+
+    public static class ViewModelFragment extends LifecycleFragment {
+        public TestViewModel fragmentModel;
+        public TestViewModel activityModel;
+        public TestViewModel defaultActivityModel;
+
+        @Override
+        public void onCreate(@Nullable Bundle savedInstanceState) {
+            super.onCreate(savedInstanceState);
+            fragmentModel = ViewModelProviders.of(this).get(KEY_FRAGMENT_MODEL,
+                    TestViewModel.class);
+            activityModel = ViewModelProviders.of(getActivity()).get(KEY_ACTIVITY_MODEL,
+                    TestViewModel.class);
+            defaultActivityModel = ViewModelProviders.of(getActivity()).get(TestViewModel.class);
+        }
+    }
+}
diff --git a/lifecycle/extensions/src/androidTest/res/layout/activity_main.xml b/lifecycle/extensions/src/androidTest/res/layout/activity_main.xml
new file mode 100644
index 0000000..305bccc
--- /dev/null
+++ b/lifecycle/extensions/src/androidTest/res/layout/activity_main.xml
@@ -0,0 +1,28 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  ~ Copyright (C) 2016 The Android Open Source Project
+  ~
+  ~ Licensed under the Apache License, Version 2.0 (the "License");
+  ~ you may not use this file except in compliance with the License.
+  ~ You may obtain a copy of the License at
+  ~
+  ~      http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing, software
+  ~ distributed under the License is distributed on an "AS IS" BASIS,
+  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  ~ See the License for the specific language governing permissions and
+  ~ limitations under the License.
+  -->
+
+<RelativeLayout
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:tools="http://schemas.android.com/tools"
+    android:id="@+id/activity_main"
+    android:layout_width="match_parent"
+    android:layout_height="match_parent"
+    tools:context="android.arch.lifecycle.activity.FragmentLifecycleActivity">
+    <FrameLayout android:id="@+id/fragment_container"
+                 android:layout_width="match_parent"
+                 android:layout_height="match_parent"></FrameLayout>
+</RelativeLayout>
diff --git a/lifecycle/extensions/src/androidTest/res/layout/activity_view_model.xml b/lifecycle/extensions/src/androidTest/res/layout/activity_view_model.xml
new file mode 100644
index 0000000..eace2e9
--- /dev/null
+++ b/lifecycle/extensions/src/androidTest/res/layout/activity_view_model.xml
@@ -0,0 +1,25 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  ~ Copyright (C) 2016 The Android Open Source Project
+  ~
+  ~ Licensed under the Apache License, Version 2.0 (the "License");
+  ~ you may not use this file except in compliance with the License.
+  ~ You may obtain a copy of the License at
+  ~
+  ~      http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing, software
+  ~ distributed under the License is distributed on an "AS IS" BASIS,
+  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  ~ See the License for the specific language governing permissions and
+  ~ limitations under the License.
+  -->
+
+<RelativeLayout
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    android:layout_width="match_parent"
+    android:layout_height="match_parent">
+    <FrameLayout android:id="@+id/fragment_container"
+                 android:layout_width="match_parent"
+                 android:layout_height="match_parent"></FrameLayout>
+</RelativeLayout>
diff --git a/lifecycle/extensions/src/main/AndroidManifest.xml b/lifecycle/extensions/src/main/AndroidManifest.xml
new file mode 100644
index 0000000..f7698eb
--- /dev/null
+++ b/lifecycle/extensions/src/main/AndroidManifest.xml
@@ -0,0 +1,25 @@
+<!--
+  ~ Copyright (C) 2016 The Android Open Source Project
+  ~
+  ~ Licensed under the Apache License, Version 2.0 (the "License");
+  ~ you may not use this file except in compliance with the License.
+  ~ You may obtain a copy of the License at
+  ~
+  ~      http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing, software
+  ~ distributed under the License is distributed on an "AS IS" BASIS,
+  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  ~ See the License for the specific language governing permissions and
+  ~ limitations under the License.
+  -->
+
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+          package="android.arch.lifecycle.extensions">
+    <application>
+        <provider android:authorities="${applicationId}.lifecycle-trojan"
+            android:multiprocess="true"
+            android:exported="false"
+            android:name="android.arch.lifecycle.LifecycleRuntimeTrojanProvider"/>
+    </application>
+</manifest>
diff --git a/lifecycle/extensions/src/main/java/android/arch/lifecycle/AndroidViewModel.java b/lifecycle/extensions/src/main/java/android/arch/lifecycle/AndroidViewModel.java
new file mode 100644
index 0000000..2c7e173
--- /dev/null
+++ b/lifecycle/extensions/src/main/java/android/arch/lifecycle/AndroidViewModel.java
@@ -0,0 +1,40 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.arch.lifecycle;
+
+import android.app.Application;
+
+/**
+ * Application context aware {@link ViewModel}.
+ * <p>
+ * Subclasses must have a constructor which accepts {@link Application} as the only parameter.
+ * <p>
+ */
+public class AndroidViewModel extends ViewModel {
+    private Application mApplication;
+
+    public AndroidViewModel(Application application) {
+        mApplication = application;
+    }
+
+    /**
+     * Return the application.
+     */
+    public <T extends Application> T getApplication() {
+        return (T) mApplication;
+    }
+}
diff --git a/lifecycle/extensions/src/main/java/android/arch/lifecycle/ComputableLiveData.java b/lifecycle/extensions/src/main/java/android/arch/lifecycle/ComputableLiveData.java
new file mode 100644
index 0000000..fe18243
--- /dev/null
+++ b/lifecycle/extensions/src/main/java/android/arch/lifecycle/ComputableLiveData.java
@@ -0,0 +1,136 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.arch.lifecycle;
+
+import android.arch.core.executor.AppToolkitTaskExecutor;
+import android.support.annotation.MainThread;
+import android.support.annotation.NonNull;
+import android.support.annotation.RestrictTo;
+import android.support.annotation.VisibleForTesting;
+import android.support.annotation.WorkerThread;
+
+import java.util.concurrent.atomic.AtomicBoolean;
+
+/**
+ * A LiveData class that can be invalidated & computed on demand.
+ * <p>
+ * This is an internal class for now, might be public if we see the necessity.
+ *
+ * @param <T> The type of the live data
+ * @hide internal
+ */
+@RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
+public abstract class ComputableLiveData<T> {
+
+    private final LiveData<T> mLiveData;
+
+    private AtomicBoolean mInvalid = new AtomicBoolean(true);
+    private AtomicBoolean mComputing = new AtomicBoolean(false);
+
+    /**
+     * Creates a computable live data which is computed when there are active observers.
+     * <p>
+     * It can also be invalidated via {@link #invalidate()} which will result in a call to
+     * {@link #compute()} if there are active observers (or when they start observing)
+     */
+    @SuppressWarnings("WeakerAccess")
+    public ComputableLiveData() {
+        mLiveData = new LiveData<T>() {
+            @Override
+            protected void onActive() {
+                // TODO if we make this class public, we should accept an executor
+                AppToolkitTaskExecutor.getInstance().executeOnDiskIO(mRefreshRunnable);
+            }
+        };
+    }
+
+    /**
+     * Returns the LiveData managed by this class.
+     *
+     * @return A LiveData that is controlled by ComputableLiveData.
+     */
+    @SuppressWarnings("WeakerAccess")
+    @NonNull
+    public LiveData<T> getLiveData() {
+        return mLiveData;
+    }
+
+    @VisibleForTesting
+    final Runnable mRefreshRunnable = new Runnable() {
+        @WorkerThread
+        @Override
+        public void run() {
+            boolean computed;
+            do {
+                computed = false;
+                // compute can happen only in 1 thread but no reason to lock others.
+                if (mComputing.compareAndSet(false, true)) {
+                    // as long as it is invalid, keep computing.
+                    try {
+                        T value = null;
+                        while (mInvalid.compareAndSet(true, false)) {
+                            computed = true;
+                            value = compute();
+                        }
+                        if (computed) {
+                            mLiveData.postValue(value);
+                        }
+                    } finally {
+                        // release compute lock
+                        mComputing.set(false);
+                    }
+                }
+                // check invalid after releasing compute lock to avoid the following scenario.
+                // Thread A runs compute()
+                // Thread A checks invalid, it is false
+                // Main thread sets invalid to true
+                // Thread B runs, fails to acquire compute lock and skips
+                // Thread A releases compute lock
+                // We've left invalid in set state. The check below recovers.
+            } while (computed && mInvalid.get());
+        }
+    };
+
+    // invalidation check always happens on the main thread
+    @VisibleForTesting
+    final Runnable mInvalidationRunnable = new Runnable() {
+        @MainThread
+        @Override
+        public void run() {
+            boolean isActive = mLiveData.hasActiveObservers();
+            if (mInvalid.compareAndSet(false, true)) {
+                if (isActive) {
+                    // TODO if we make this class public, we should accept an executor.
+                    AppToolkitTaskExecutor.getInstance().executeOnDiskIO(mRefreshRunnable);
+                }
+            }
+        }
+    };
+
+    /**
+     * Invalidates the LiveData.
+     * <p>
+     * When there are active observers, this will trigger a call to {@link #compute()}.
+     */
+    public void invalidate() {
+        AppToolkitTaskExecutor.getInstance().executeOnMainThread(mInvalidationRunnable);
+    }
+
+    @SuppressWarnings("WeakerAccess")
+    @WorkerThread
+    protected abstract T compute();
+}
diff --git a/lifecycle/extensions/src/main/java/android/arch/lifecycle/EmptyActivityLifecycleCallbacks.java b/lifecycle/extensions/src/main/java/android/arch/lifecycle/EmptyActivityLifecycleCallbacks.java
new file mode 100644
index 0000000..ed41f2d
--- /dev/null
+++ b/lifecycle/extensions/src/main/java/android/arch/lifecycle/EmptyActivityLifecycleCallbacks.java
@@ -0,0 +1,51 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.arch.lifecycle;
+
+import android.app.Activity;
+import android.app.Application;
+import android.os.Bundle;
+
+class EmptyActivityLifecycleCallbacks implements Application.ActivityLifecycleCallbacks {
+    @Override
+    public void onActivityCreated(Activity activity, Bundle savedInstanceState) {
+    }
+
+    @Override
+    public void onActivityStarted(Activity activity) {
+    }
+
+    @Override
+    public void onActivityResumed(Activity activity) {
+    }
+
+    @Override
+    public void onActivityPaused(Activity activity) {
+    }
+
+    @Override
+    public void onActivityStopped(Activity activity) {
+    }
+
+    @Override
+    public void onActivitySaveInstanceState(Activity activity, Bundle outState) {
+    }
+
+    @Override
+    public void onActivityDestroyed(Activity activity) {
+    }
+}
diff --git a/lifecycle/extensions/src/main/java/android/arch/lifecycle/HolderFragment.java b/lifecycle/extensions/src/main/java/android/arch/lifecycle/HolderFragment.java
new file mode 100644
index 0000000..100d10a
--- /dev/null
+++ b/lifecycle/extensions/src/main/java/android/arch/lifecycle/HolderFragment.java
@@ -0,0 +1,191 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.arch.lifecycle;
+
+import android.app.Activity;
+import android.app.Application.ActivityLifecycleCallbacks;
+import android.os.Bundle;
+import android.support.annotation.Nullable;
+import android.support.annotation.RestrictTo;
+import android.support.v4.app.Fragment;
+import android.support.v4.app.FragmentActivity;
+import android.support.v4.app.FragmentManager;
+import android.support.v4.app.FragmentManager.FragmentLifecycleCallbacks;
+import android.util.Log;
+
+import java.util.HashMap;
+import java.util.Map;
+
+/**
+ * @hide
+ */
+@RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
+public class HolderFragment extends Fragment {
+    private static final String LOG_TAG = "ViewModelStores";
+
+    private static final HolderFragmentManager sHolderFragmentManager = new HolderFragmentManager();
+
+    /**
+     * @hide
+     */
+    @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
+    public static final String HOLDER_TAG =
+            "android.arch.lifecycle.state.StateProviderHolderFragment";
+
+    private ViewModelStore mViewModelStore = new ViewModelStore();
+
+    public HolderFragment() {
+        setRetainInstance(true);
+    }
+
+    @Override
+    public void onCreate(@Nullable Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+        sHolderFragmentManager.holderFragmentCreated(this);
+    }
+
+    @Override
+    public void onSaveInstanceState(Bundle outState) {
+        super.onSaveInstanceState(outState);
+    }
+
+    @Override
+    public void onDestroy() {
+        super.onDestroy();
+        mViewModelStore.clear();
+    }
+
+    public ViewModelStore getViewModelStore() {
+        return mViewModelStore;
+    }
+
+    /**
+     * @hide
+     */
+    @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
+    public static HolderFragment holderFragmentFor(FragmentActivity activity) {
+        return sHolderFragmentManager.holderFragmentFor(activity);
+    }
+
+    /**
+     * @hide
+     */
+    @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
+    public static HolderFragment holderFragmentFor(Fragment fragment) {
+        return sHolderFragmentManager.holderFragmentFor(fragment);
+    }
+
+    @SuppressWarnings("WeakerAccess")
+    static class HolderFragmentManager {
+        private Map<Activity, HolderFragment> mNotCommittedActivityHolders = new HashMap<>();
+        private Map<Fragment, HolderFragment> mNotCommittedFragmentHolders = new HashMap<>();
+
+        private ActivityLifecycleCallbacks mActivityCallbacks =
+                new EmptyActivityLifecycleCallbacks() {
+                    @Override
+                    public void onActivityDestroyed(Activity activity) {
+                        HolderFragment fragment = mNotCommittedActivityHolders.remove(activity);
+                        if (fragment != null) {
+                            Log.e(LOG_TAG, "Failed to save a ViewModel for " + activity);
+                        }
+                    }
+                };
+
+        private boolean mActivityCallbacksIsAdded = false;
+
+        private FragmentLifecycleCallbacks mParentDestroyedCallback =
+                new FragmentLifecycleCallbacks() {
+                    @Override
+                    public void onFragmentDestroyed(FragmentManager fm, Fragment parentFragment) {
+                        super.onFragmentDestroyed(fm, parentFragment);
+                        HolderFragment fragment = mNotCommittedFragmentHolders.remove(
+                                parentFragment);
+                        if (fragment != null) {
+                            Log.e(LOG_TAG, "Failed to save a ViewModel for " + parentFragment);
+                        }
+                    }
+                };
+
+        void holderFragmentCreated(Fragment holderFragment) {
+            Fragment parentFragment = holderFragment.getParentFragment();
+            if (parentFragment != null) {
+                mNotCommittedFragmentHolders.remove(parentFragment);
+                parentFragment.getFragmentManager().unregisterFragmentLifecycleCallbacks(
+                        mParentDestroyedCallback);
+            } else {
+                mNotCommittedActivityHolders.remove(holderFragment.getActivity());
+            }
+        }
+
+        private static HolderFragment findHolderFragment(FragmentManager manager) {
+            if (manager.isDestroyed()) {
+                throw new IllegalStateException("Can't access ViewModels from onDestroy");
+            }
+
+            Fragment fragmentByTag = manager.findFragmentByTag(HOLDER_TAG);
+            if (fragmentByTag != null && !(fragmentByTag instanceof HolderFragment)) {
+                throw new IllegalStateException("Unexpected "
+                        + "fragment instance was returned by HOLDER_TAG");
+            }
+            return (HolderFragment) fragmentByTag;
+        }
+
+        private static HolderFragment createHolderFragment(FragmentManager fragmentManager) {
+            HolderFragment holder = new HolderFragment();
+            fragmentManager.beginTransaction().add(holder, HOLDER_TAG).commitAllowingStateLoss();
+            return holder;
+        }
+
+        HolderFragment holderFragmentFor(FragmentActivity activity) {
+            FragmentManager fm = activity.getSupportFragmentManager();
+            HolderFragment holder = findHolderFragment(fm);
+            if (holder != null) {
+                return holder;
+            }
+            holder = mNotCommittedActivityHolders.get(activity);
+            if (holder != null) {
+                return holder;
+            }
+
+            if (!mActivityCallbacksIsAdded) {
+                mActivityCallbacksIsAdded = true;
+                activity.getApplication().registerActivityLifecycleCallbacks(mActivityCallbacks);
+            }
+            holder = createHolderFragment(fm);
+            mNotCommittedActivityHolders.put(activity, holder);
+            return holder;
+        }
+
+        HolderFragment holderFragmentFor(Fragment parentFragment) {
+            FragmentManager fm = parentFragment.getChildFragmentManager();
+            HolderFragment holder = findHolderFragment(fm);
+            if (holder != null) {
+                return holder;
+            }
+            holder = mNotCommittedFragmentHolders.get(parentFragment);
+            if (holder != null) {
+                return holder;
+            }
+
+            parentFragment.getFragmentManager()
+                    .registerFragmentLifecycleCallbacks(mParentDestroyedCallback, false);
+            holder = createHolderFragment(fm);
+            mNotCommittedFragmentHolders.put(parentFragment, holder);
+            return holder;
+        }
+    }
+}
diff --git a/lifecycle/extensions/src/main/java/android/arch/lifecycle/LifecycleActivity.java b/lifecycle/extensions/src/main/java/android/arch/lifecycle/LifecycleActivity.java
new file mode 100644
index 0000000..196a8d7
--- /dev/null
+++ b/lifecycle/extensions/src/main/java/android/arch/lifecycle/LifecycleActivity.java
@@ -0,0 +1,35 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.arch.lifecycle;
+
+import android.support.v4.app.FragmentActivity;
+
+/**
+ * Activity that implements {@link LifecycleOwner}.
+ * <p>
+ * This class is a temporary implementation detail until Lifecycles are integrated with support
+ * library.
+ */
+public class LifecycleActivity extends FragmentActivity implements LifecycleRegistryOwner {
+
+    private final LifecycleRegistry mRegistry = new LifecycleRegistry(this);
+
+    @Override
+    public LifecycleRegistry getLifecycle() {
+        return mRegistry;
+    }
+}
diff --git a/lifecycle/extensions/src/main/java/android/arch/lifecycle/LifecycleDispatcher.java b/lifecycle/extensions/src/main/java/android/arch/lifecycle/LifecycleDispatcher.java
new file mode 100644
index 0000000..9fdec95
--- /dev/null
+++ b/lifecycle/extensions/src/main/java/android/arch/lifecycle/LifecycleDispatcher.java
@@ -0,0 +1,182 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.arch.lifecycle;
+
+import static android.arch.lifecycle.Lifecycle.Event.ON_CREATE;
+import static android.arch.lifecycle.Lifecycle.Event.ON_DESTROY;
+import static android.arch.lifecycle.Lifecycle.Event.ON_PAUSE;
+import static android.arch.lifecycle.Lifecycle.Event.ON_RESUME;
+import static android.arch.lifecycle.Lifecycle.Event.ON_START;
+import static android.arch.lifecycle.Lifecycle.Event.ON_STOP;
+import static android.arch.lifecycle.Lifecycle.State.CREATED;
+
+import android.app.Activity;
+import android.app.Application;
+import android.arch.lifecycle.Lifecycle.State;
+import android.content.Context;
+import android.os.Bundle;
+import android.support.annotation.VisibleForTesting;
+import android.support.v4.app.Fragment;
+import android.support.v4.app.FragmentActivity;
+import android.support.v4.app.FragmentManager;
+
+import java.util.Collection;
+import java.util.concurrent.atomic.AtomicBoolean;
+
+/**
+ * When initialized, it hooks into the Activity callback of the Application and observes
+ * Activities. It is responsible to hook in child-fragments to activities and fragments to report
+ * their lifecycle events. Another responsibility of this class is to mark as stopped all lifecycle
+ * providers related to an activity as soon it is not safe to run a fragment transaction in this
+ * activity.
+ */
+class LifecycleDispatcher {
+
+    private static final String REPORT_FRAGMENT_TAG = "android.arch.lifecycle"
+            + ".LifecycleDispatcher.report_fragment_tag";
+
+    private static AtomicBoolean sInitialized = new AtomicBoolean(false);
+
+    static void init(Context context) {
+        if (sInitialized.getAndSet(true)) {
+            return;
+        }
+        ((Application) context.getApplicationContext())
+                .registerActivityLifecycleCallbacks(new DispatcherActivityCallback());
+    }
+
+    @SuppressWarnings("WeakerAccess")
+    @VisibleForTesting
+    static class DispatcherActivityCallback extends EmptyActivityLifecycleCallbacks {
+        private final FragmentCallback mFragmentCallback;
+
+        DispatcherActivityCallback() {
+            mFragmentCallback = new FragmentCallback();
+        }
+
+        @Override
+        public void onActivityCreated(Activity activity, Bundle savedInstanceState) {
+            if (activity instanceof FragmentActivity) {
+                ((FragmentActivity) activity).getSupportFragmentManager()
+                        .registerFragmentLifecycleCallbacks(mFragmentCallback, true);
+            }
+            ReportFragment.injectIfNeededIn(activity);
+        }
+
+        @Override
+        public void onActivityStopped(Activity activity) {
+            if (activity instanceof FragmentActivity) {
+                markState((FragmentActivity) activity, CREATED);
+            }
+        }
+
+        @Override
+        public void onActivitySaveInstanceState(Activity activity, Bundle outState) {
+            if (activity instanceof FragmentActivity) {
+                markState((FragmentActivity) activity, CREATED);
+            }
+        }
+    }
+
+    @SuppressWarnings("WeakerAccess")
+    public static class DestructionReportFragment extends Fragment {
+        @Override
+        public void onPause() {
+            super.onPause();
+            dispatch(ON_PAUSE);
+        }
+
+        @Override
+        public void onStop() {
+            super.onStop();
+            dispatch(ON_STOP);
+        }
+
+        @Override
+        public void onDestroy() {
+            super.onDestroy();
+            dispatch(ON_DESTROY);
+        }
+
+        protected void dispatch(Lifecycle.Event event) {
+            dispatchIfLifecycleOwner(getParentFragment(), event);
+        }
+    }
+
+    private static void markState(FragmentManager manager, State state) {
+        Collection<Fragment> fragments = manager.getFragments();
+        if (fragments == null) {
+            return;
+        }
+        for (Fragment fragment : fragments) {
+            if (fragment == null) {
+                continue;
+            }
+            markStateIn(fragment, state);
+            if (fragment.isAdded()) {
+                markState(fragment.getChildFragmentManager(), state);
+            }
+        }
+    }
+
+    private static void markStateIn(Object object, State state) {
+        if (object instanceof LifecycleRegistryOwner) {
+            LifecycleRegistry registry = ((LifecycleRegistryOwner) object).getLifecycle();
+            registry.markState(state);
+        }
+    }
+
+    private static void markState(FragmentActivity activity, State state) {
+        markStateIn(activity, state);
+        markState(activity.getSupportFragmentManager(), state);
+    }
+
+    private static void dispatchIfLifecycleOwner(Fragment fragment, Lifecycle.Event event) {
+        if (fragment instanceof LifecycleRegistryOwner) {
+            ((LifecycleRegistryOwner) fragment).getLifecycle().handleLifecycleEvent(event);
+        }
+    }
+
+    @SuppressWarnings("WeakerAccess")
+    @VisibleForTesting
+    static class FragmentCallback extends FragmentManager.FragmentLifecycleCallbacks {
+
+        @Override
+        public void onFragmentCreated(FragmentManager fm, Fragment f, Bundle savedInstanceState) {
+            dispatchIfLifecycleOwner(f, ON_CREATE);
+
+            if (!(f instanceof LifecycleRegistryOwner)) {
+                return;
+            }
+
+            if (f.getChildFragmentManager().findFragmentByTag(REPORT_FRAGMENT_TAG) == null) {
+                f.getChildFragmentManager().beginTransaction().add(new DestructionReportFragment(),
+                        REPORT_FRAGMENT_TAG).commit();
+            }
+        }
+
+        @Override
+        public void onFragmentStarted(FragmentManager fm, Fragment f) {
+            dispatchIfLifecycleOwner(f, ON_START);
+        }
+
+        @Override
+        public void onFragmentResumed(FragmentManager fm, Fragment f) {
+            dispatchIfLifecycleOwner(f, ON_RESUME);
+        }
+    }
+}
diff --git a/lifecycle/extensions/src/main/java/android/arch/lifecycle/LifecycleFragment.java b/lifecycle/extensions/src/main/java/android/arch/lifecycle/LifecycleFragment.java
new file mode 100644
index 0000000..d8dbf38
--- /dev/null
+++ b/lifecycle/extensions/src/main/java/android/arch/lifecycle/LifecycleFragment.java
@@ -0,0 +1,35 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.arch.lifecycle;
+
+import android.support.v4.app.Fragment;
+
+/**
+ * A fragment that is also a {@link LifecycleOwner}.
+ * <p>
+ * This class is a temporary implementation detail until Lifecycles are integrated with support
+ * library.
+ */
+// This class will be removed once we integrate with Fragment library.
+public class LifecycleFragment extends Fragment implements LifecycleRegistryOwner {
+    LifecycleRegistry mLifecycleRegistry = new LifecycleRegistry(this);
+
+    @Override
+    public LifecycleRegistry getLifecycle() {
+        return mLifecycleRegistry;
+    }
+}
diff --git a/lifecycle/extensions/src/main/java/android/arch/lifecycle/LifecycleRuntimeTrojanProvider.java b/lifecycle/extensions/src/main/java/android/arch/lifecycle/LifecycleRuntimeTrojanProvider.java
new file mode 100644
index 0000000..ac278c0
--- /dev/null
+++ b/lifecycle/extensions/src/main/java/android/arch/lifecycle/LifecycleRuntimeTrojanProvider.java
@@ -0,0 +1,68 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.arch.lifecycle;
+
+import android.content.ContentProvider;
+import android.content.ContentValues;
+import android.database.Cursor;
+import android.net.Uri;
+import android.support.annotation.NonNull;
+import android.support.annotation.Nullable;
+import android.support.annotation.RestrictTo;
+
+/**
+ * Internal class to initialize Lifecycles.
+ * @hide
+ */
+@RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
+public class LifecycleRuntimeTrojanProvider extends ContentProvider {
+    @Override
+    public boolean onCreate() {
+        LifecycleDispatcher.init(getContext());
+        ProcessLifecycleOwner.init(getContext());
+        return true;
+    }
+
+    @Nullable
+    @Override
+    public Cursor query(@NonNull Uri uri, String[] strings, String s, String[] strings1,
+            String s1) {
+        return null;
+    }
+
+    @Nullable
+    @Override
+    public String getType(@NonNull Uri uri) {
+        return null;
+    }
+
+    @Nullable
+    @Override
+    public Uri insert(@NonNull Uri uri, ContentValues contentValues) {
+        return null;
+    }
+
+    @Override
+    public int delete(@NonNull Uri uri, String s, String[] strings) {
+        return 0;
+    }
+
+    @Override
+    public int update(@NonNull Uri uri, ContentValues contentValues, String s, String[] strings) {
+        return 0;
+    }
+}
diff --git a/lifecycle/extensions/src/main/java/android/arch/lifecycle/LifecycleService.java b/lifecycle/extensions/src/main/java/android/arch/lifecycle/LifecycleService.java
new file mode 100644
index 0000000..adc4ffc
--- /dev/null
+++ b/lifecycle/extensions/src/main/java/android/arch/lifecycle/LifecycleService.java
@@ -0,0 +1,76 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.arch.lifecycle;
+
+import android.app.Service;
+import android.content.Intent;
+import android.os.IBinder;
+import android.support.annotation.CallSuper;
+import android.support.annotation.Nullable;
+
+/**
+ * A Service that is also a {@link LifecycleOwner}.
+ */
+public class LifecycleService extends Service implements LifecycleOwner {
+
+    private final ServiceLifecycleDispatcher mDispatcher = new ServiceLifecycleDispatcher(this);
+
+    @CallSuper
+    @Override
+    public void onCreate() {
+        mDispatcher.onServicePreSuperOnCreate();
+        super.onCreate();
+    }
+
+    @CallSuper
+    @Nullable
+    @Override
+    public IBinder onBind(Intent intent) {
+        mDispatcher.onServicePreSuperOnBind();
+        return null;
+    }
+
+    @SuppressWarnings("deprecation")
+    @CallSuper
+    @Override
+    public void onStart(Intent intent, int startId) {
+        mDispatcher.onServicePreSuperOnStart();
+        super.onStart(intent, startId);
+    }
+
+    // this method is added only to annotate it with @CallSuper.
+    // In usual service super.onStartCommand is no-op, but in LifecycleService
+    // it results in mDispatcher.onServicePreSuperOnStart() call, because
+    // super.onStartCommand calls onStart().
+    @CallSuper
+    @Override
+    public int onStartCommand(Intent intent, int flags, int startId) {
+        return super.onStartCommand(intent, flags, startId);
+    }
+
+    @CallSuper
+    @Override
+    public void onDestroy() {
+        mDispatcher.onServicePreSuperOnDestroy();
+        super.onDestroy();
+    }
+
+    @Override
+    public Lifecycle getLifecycle() {
+        return mDispatcher.getLifecycle();
+    }
+}
diff --git a/lifecycle/extensions/src/main/java/android/arch/lifecycle/LiveData.java b/lifecycle/extensions/src/main/java/android/arch/lifecycle/LiveData.java
new file mode 100644
index 0000000..d689442
--- /dev/null
+++ b/lifecycle/extensions/src/main/java/android/arch/lifecycle/LiveData.java
@@ -0,0 +1,413 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.arch.lifecycle;
+
+import static android.arch.lifecycle.Lifecycle.State.DESTROYED;
+import static android.arch.lifecycle.Lifecycle.State.STARTED;
+
+import android.arch.core.executor.AppToolkitTaskExecutor;
+import android.arch.core.internal.SafeIterableMap;
+import android.arch.lifecycle.Lifecycle.State;
+import android.support.annotation.MainThread;
+import android.support.annotation.Nullable;
+
+import java.util.Iterator;
+import java.util.Map;
+
+/**
+ * LiveData is a data holder class that can be observed within a given lifecycle.
+ * This means that an {@link Observer} can be added in a pair with a {@link LifecycleOwner}, and
+ * this observer will be notified about modifications of the wrapped data only if the paired
+ * LifecycleOwner is in active state. LifecycleOwner is considered as active, if its state is
+ * {@link Lifecycle.State#STARTED} or {@link Lifecycle.State#RESUMED}. An observer added via
+ * {@link #observeForever(Observer)} is considered as always active and thus will be always notified
+ * about modifications. For those observers, you should manually call
+ * {@link #removeObserver(Observer)}.
+ *
+ * <p> An observer added with a Lifecycle will be automatically removed if the corresponding
+ * Lifecycle moves to {@link Lifecycle.State#DESTROYED} state. This is especially useful for
+ * activities and fragments where they can safely observe LiveData and not worry about leaks:
+ * they will be instantly unsubscribed when they are destroyed.
+ *
+ * <p>
+ * In addition, LiveData has {@link LiveData#onActive()} and {@link LiveData#onInactive()} methods
+ * to get notified when number of active {@link Observer}s change between 0 and 1.
+ * This allows LiveData to release any heavy resources when it does not have any Observers that
+ * are actively observing.
+ * <p>
+ * This class is designed to hold individual data fields of {@link ViewModel},
+ * but can also be used for sharing data between different modules in your application
+ * in a decoupled fashion.
+ *
+ * @param <T> The type of data hold by this instance
+ * @see ViewModel
+ */
+@SuppressWarnings({"WeakerAccess", "unused"})
+// TODO: Thread checks are too strict right now, we may consider automatically moving them to main
+// thread.
+public abstract class LiveData<T> {
+    private final Object mDataLock = new Object();
+    static final int START_VERSION = -1;
+    private static final Object NOT_SET = new Object();
+
+    private static final LifecycleOwner ALWAYS_ON = new LifecycleOwner() {
+
+        private LifecycleRegistry mRegistry = init();
+
+        private LifecycleRegistry init() {
+            LifecycleRegistry registry = new LifecycleRegistry(this);
+            registry.handleLifecycleEvent(Lifecycle.Event.ON_CREATE);
+            registry.handleLifecycleEvent(Lifecycle.Event.ON_START);
+            registry.handleLifecycleEvent(Lifecycle.Event.ON_RESUME);
+            return registry;
+        }
+
+        @Override
+        public Lifecycle getLifecycle() {
+            return mRegistry;
+        }
+    };
+
+    private SafeIterableMap<Observer<T>, LifecycleBoundObserver> mObservers =
+            new SafeIterableMap<>();
+
+    // how many observers are in active state
+    private int mActiveCount = 0;
+    private Object mData = NOT_SET;
+    // when setData is called, we set the pending data and actual data swap happens on the main
+    // thread
+    private volatile Object mPendingData = NOT_SET;
+    private int mVersion = START_VERSION;
+
+    private boolean mDispatchingValue;
+    @SuppressWarnings("FieldCanBeLocal")
+    private boolean mDispatchInvalidated;
+    private final Runnable mPostValueRunnable = new Runnable() {
+        @Override
+        public void run() {
+            Object newValue;
+            synchronized (mDataLock) {
+                newValue = mPendingData;
+                mPendingData = NOT_SET;
+            }
+            //noinspection unchecked
+            setValue((T) newValue);
+        }
+    };
+
+    private void considerNotify(LifecycleBoundObserver observer) {
+        if (!observer.active) {
+            return;
+        }
+        // Check latest state b4 dispatch. Maybe it changed state but we didn't get the event yet.
+        //
+        // we still first check observer.active to keep it as the entrance for events. So even if
+        // the observer moved to an active state, if we've not received that event, we better not
+        // notify for a more predictable notification order.
+        if (!isActiveState(observer.owner.getLifecycle().getCurrentState())) {
+            return;
+        }
+        if (observer.lastVersion >= mVersion) {
+            return;
+        }
+        observer.lastVersion = mVersion;
+        //noinspection unchecked
+        observer.observer.onChanged((T) mData);
+    }
+
+    private void dispatchingValue(@Nullable LifecycleBoundObserver initiator) {
+        if (mDispatchingValue) {
+            mDispatchInvalidated = true;
+            return;
+        }
+        mDispatchingValue = true;
+        do {
+            mDispatchInvalidated = false;
+            if (initiator != null) {
+                considerNotify(initiator);
+                initiator = null;
+            } else {
+                for (Iterator<Map.Entry<Observer<T>, LifecycleBoundObserver>> iterator =
+                        mObservers.iteratorWithAdditions(); iterator.hasNext(); ) {
+                    considerNotify(iterator.next().getValue());
+                    if (mDispatchInvalidated) {
+                        break;
+                    }
+                }
+            }
+        } while (mDispatchInvalidated);
+        mDispatchingValue = false;
+    }
+
+    /**
+     * Adds the given observer to the observers list within the lifespan of the given
+     * owner. The events are dispatched on the main thread. If LiveData already has data
+     * set, it will be delivered to the observer.
+     * <p>
+     * The observer will only receive events if the owner is in {@link Lifecycle.State#STARTED}
+     * or {@link Lifecycle.State#RESUMED} state (active).
+     * <p>
+     * If the owner moves to the {@link Lifecycle.State#DESTROYED} state, the observer will
+     * automatically be removed.
+     * <p>
+     * When data changes while the {@code owner} is not active, it will not receive any updates.
+     * If it becomes active again, it will receive the last available data automatically.
+     * <p>
+     * LiveData keeps a strong reference to the observer and the owner as long as the
+     * given LifecycleOwner is not destroyed. When it is destroyed, LiveData removes references to
+     * the observer &amp; the owner.
+     * <p>
+     * If the given owner is already in {@link Lifecycle.State#DESTROYED} state, LiveData
+     * ignores the call.
+     * <p>
+     * If the given owner, observer tuple is already in the list, the call is ignored.
+     * If the observer is already in the list with another owner, LiveData throws an
+     * {@link IllegalArgumentException}.
+     *
+     * @param owner    The LifecycleOwner which controls the observer
+     * @param observer The observer that will receive the events
+     */
+    @MainThread
+    public void observe(LifecycleOwner owner, Observer<T> observer) {
+        if (owner.getLifecycle().getCurrentState() == DESTROYED) {
+            // ignore
+            return;
+        }
+        LifecycleBoundObserver wrapper = new LifecycleBoundObserver(owner, observer);
+        LifecycleBoundObserver existing = mObservers.putIfAbsent(observer, wrapper);
+        if (existing != null && existing.owner != wrapper.owner) {
+            throw new IllegalArgumentException("Cannot add the same observer"
+                    + " with different lifecycles");
+        }
+        if (existing != null) {
+            return;
+        }
+        owner.getLifecycle().addObserver(wrapper);
+        wrapper.activeStateChanged(isActiveState(owner.getLifecycle().getCurrentState()));
+    }
+
+    /**
+     * Adds the given observer to the observers list. This call is similar to
+     * {@link LiveData#observe(LifecycleOwner, Observer)} with a LifecycleOwner, which
+     * is always active. This means that the given observer will receive all events and will never
+     * be automatically removed. You should manually call {@link #removeObserver(Observer)} to stop
+     * observing this LiveData.
+     * While LiveData has one of such observers, it will be considered
+     * as active.
+     * <p>
+     * If the observer was already added with an owner to this LiveData, LiveData throws an
+     * {@link IllegalArgumentException}.
+     *
+     * @param observer The observer that will receive the events
+     */
+    @MainThread
+    public void observeForever(Observer<T> observer) {
+        observe(ALWAYS_ON, observer);
+    }
+
+    /**
+     * Removes the given observer from the observers list.
+     *
+     * @param observer The Observer to receive events.
+     */
+    @MainThread
+    public void removeObserver(final Observer<T> observer) {
+        assertMainThread("removeObserver");
+        LifecycleBoundObserver removed = mObservers.remove(observer);
+        if (removed == null) {
+            return;
+        }
+        removed.owner.getLifecycle().removeObserver(removed);
+        removed.activeStateChanged(false);
+    }
+
+    /**
+     * Removes all observers that are tied to the given {@link LifecycleOwner}.
+     *
+     * @param owner The {@code LifecycleOwner} scope for the observers to be removed.
+     */
+    @MainThread
+    public void removeObservers(final LifecycleOwner owner) {
+        assertMainThread("removeObservers");
+        for (Map.Entry<Observer<T>, LifecycleBoundObserver> entry : mObservers) {
+            if (entry.getValue().owner == owner) {
+                removeObserver(entry.getKey());
+            }
+        }
+    }
+
+    /**
+     * Posts a task to a main thread to set the given value. So if you have a following code
+     * executed in the main thread:
+     * <pre class="prettyprint">
+     * liveData.postValue("a");
+     * liveData.setValue("b");
+     * </pre>
+     * The value "b" would be set at first and later the main thread would override it with
+     * the value "a".
+     * <p>
+     * If you called this method multiple times before a main thread executed a posted task, only
+     * the last value would be dispatched.
+     *
+     * @param value The new value
+     */
+    protected void postValue(T value) {
+        boolean postTask;
+        synchronized (mDataLock) {
+            postTask = mPendingData == NOT_SET;
+            mPendingData = value;
+        }
+        if (!postTask) {
+            return;
+        }
+        AppToolkitTaskExecutor.getInstance().postToMainThread(mPostValueRunnable);
+    }
+
+    /**
+     * Sets the value. If there are active observers, the value will be dispatched to them.
+     * <p>
+     * This method must be called from the main thread. If you need set a value from a background
+     * thread, you can use {@link #postValue(Object)}
+     *
+     * @param value The new value
+     */
+    @MainThread
+    protected void setValue(T value) {
+        assertMainThread("setValue");
+        mVersion++;
+        mData = value;
+        dispatchingValue(null);
+    }
+
+    /**
+     * Returns the current value.
+     * Note that calling this method on a background thread does not guarantee that the latest
+     * value set will be received.
+     *
+     * @return the current value
+     */
+    @Nullable
+    public T getValue() {
+        // we do not return pending data here to be able to serve a consistent view to the main
+        // thread.
+        Object data = mData;
+        if (mData != NOT_SET) {
+            //noinspection unchecked
+            return (T) mData;
+        }
+        return null;
+    }
+
+    int getVersion() {
+        return mVersion;
+    }
+
+    /**
+     * Called when the number of active observers change to 1 from 0.
+     * <p>
+     * This callback can be used to know that this LiveData is being used thus should be kept
+     * up to date.
+     */
+    protected void onActive() {
+
+    }
+
+    /**
+     * Called when the number of active observers change from 1 to 0.
+     * <p>
+     * This does not mean that there are no observers left, there may still be observers but their
+     * lifecycle states aren't {@link Lifecycle.State#STARTED} or {@link Lifecycle.State#RESUMED}
+     * (like an Activity in the back stack).
+     * <p>
+     * You can check if there are observers via {@link #hasObservers()}.
+     */
+    protected void onInactive() {
+
+    }
+
+    /**
+     * Returns true if this LiveData has observers.
+     *
+     * @return true if this LiveData has observers
+     */
+    public boolean hasObservers() {
+        return mObservers.size() > 0;
+    }
+
+    /**
+     * Returns true if this LiveData has active observers.
+     *
+     * @return true if this LiveData has active observers
+     */
+    public boolean hasActiveObservers() {
+        return mActiveCount > 0;
+    }
+
+    class LifecycleBoundObserver implements LifecycleObserver {
+        public final LifecycleOwner owner;
+        public final Observer<T> observer;
+        public boolean active;
+        public int lastVersion = START_VERSION;
+
+        LifecycleBoundObserver(LifecycleOwner owner, Observer<T> observer) {
+            this.owner = owner;
+            this.observer = observer;
+        }
+
+        @SuppressWarnings("unused")
+        @OnLifecycleEvent(Lifecycle.Event.ON_ANY)
+        void onStateChange() {
+            if (owner.getLifecycle().getCurrentState() == DESTROYED) {
+                removeObserver(observer);
+                return;
+            }
+            // immediately set active state, so we'd never dispatch anything to inactive
+            // owner
+            activeStateChanged(isActiveState(owner.getLifecycle().getCurrentState()));
+
+        }
+
+        void activeStateChanged(boolean newActive) {
+            if (newActive == active) {
+                return;
+            }
+            active = newActive;
+            boolean wasInactive = LiveData.this.mActiveCount == 0;
+            LiveData.this.mActiveCount += active ? 1 : -1;
+            if (wasInactive && active) {
+                onActive();
+            }
+            if (LiveData.this.mActiveCount == 0 && !active) {
+                onInactive();
+            }
+            if (active) {
+                dispatchingValue(this);
+            }
+        }
+    }
+
+    static boolean isActiveState(State state) {
+        return state.isAtLeast(STARTED);
+    }
+
+    private void assertMainThread(String methodName) {
+        if (!AppToolkitTaskExecutor.getInstance().isMainThread()) {
+            throw new IllegalStateException("Cannot invoke " + methodName + " on a background"
+                    + " thread");
+        }
+    }
+}
diff --git a/lifecycle/extensions/src/main/java/android/arch/lifecycle/MediatorLiveData.java b/lifecycle/extensions/src/main/java/android/arch/lifecycle/MediatorLiveData.java
new file mode 100644
index 0000000..a7d090b
--- /dev/null
+++ b/lifecycle/extensions/src/main/java/android/arch/lifecycle/MediatorLiveData.java
@@ -0,0 +1,123 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.arch.lifecycle;
+
+import android.arch.core.internal.SafeIterableMap;
+import android.support.annotation.CallSuper;
+import android.support.annotation.MainThread;
+import android.support.annotation.Nullable;
+
+import java.util.Map;
+
+/**
+ * {@link LiveData} subclass which may observer other {@code LiveData} objects and react on
+ * {@code OnChanged} events from them.
+ * <p>
+ * This class correctly propagates its active/inactive states down to source {@code LiveData}
+ * objects.
+ *
+ * @param <T> The type of data hold by this instance
+ */
+@SuppressWarnings("WeakerAccess")
+public class MediatorLiveData<T> extends MutableLiveData<T> {
+    private SafeIterableMap<LiveData<?>, Source<?>> mSources = new SafeIterableMap<>();
+
+    /**
+     * Starts to listen the given {@code source} LiveData, {@code onChanged} observer will be called
+     * when {@code source} value was changed.
+     * <p>
+     * {@code onChanged} callback will be called only when this {@code MediatorLiveData} is active.
+     * <p> If the given LiveData is already added as a source but with a different Observer,
+     * {@link IllegalArgumentException} will be thrown.
+     *
+     * @param source    the {@code LiveData} to listen to
+     * @param onChanged The observer that will receive the events
+     * @param <S>       The type of data hold by {@code source} LiveData
+     */
+    @MainThread
+    public <S> void addSource(LiveData<S> source, Observer<S> onChanged) {
+        Source<S> e = new Source<>(source, onChanged);
+        Source<?> existing = mSources.putIfAbsent(source, e);
+        if (existing != null && existing.mObserver != onChanged) {
+            throw new IllegalArgumentException(
+                    "This source was already added with the different observer");
+        }
+        if (existing != null) {
+            return;
+        }
+        if (hasActiveObservers()) {
+            e.plug();
+        }
+    }
+
+    /**
+     * Stops to listen the given {@code LiveData}.
+     *
+     * @param toRemote {@code LiveData} to stop to listen
+     * @param <S>      the type of data hold by {@code source} LiveData
+     */
+    @MainThread
+    public <S> void removeSource(LiveData<S> toRemote) {
+        Source<?> source = mSources.remove(toRemote);
+        if (source != null) {
+            source.unplug();
+        }
+    }
+
+    @CallSuper
+    @Override
+    protected void onActive() {
+        for (Map.Entry<LiveData<?>, Source<?>> source : mSources) {
+            source.getValue().plug();
+        }
+    }
+
+    @CallSuper
+    @Override
+    protected void onInactive() {
+        for (Map.Entry<LiveData<?>, Source<?>> source : mSources) {
+            source.getValue().unplug();
+        }
+    }
+
+    private static class Source<V> {
+        final LiveData<V> mLiveData;
+        final Observer<V> mObserver;
+        int mVersion = START_VERSION;
+
+        Source(LiveData<V> liveData, final Observer<V> observer) {
+            mLiveData = liveData;
+            mObserver = new Observer<V>() {
+                @Override
+                public void onChanged(@Nullable V v) {
+                    if (mVersion != mLiveData.getVersion()) {
+                        mVersion = mLiveData.getVersion();
+                        observer.onChanged(v);
+                    }
+                }
+            };
+        }
+
+        void plug() {
+            mLiveData.observeForever(mObserver);
+        }
+
+        void unplug() {
+            mLiveData.removeObserver(mObserver);
+        }
+    }
+}
diff --git a/lifecycle/extensions/src/main/java/android/arch/lifecycle/MutableLiveData.java b/lifecycle/extensions/src/main/java/android/arch/lifecycle/MutableLiveData.java
new file mode 100644
index 0000000..ecd7752
--- /dev/null
+++ b/lifecycle/extensions/src/main/java/android/arch/lifecycle/MutableLiveData.java
@@ -0,0 +1,35 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.arch.lifecycle;
+
+/**
+ * {@link LiveData} which publicly exposes {@link #setValue(T)} and {@link #postValue(T)} method.
+ *
+ * @param <T> The type of data hold by this instance
+ */
+@SuppressWarnings("WeakerAccess")
+public class MutableLiveData<T> extends LiveData<T> {
+    @Override
+    public void postValue(T value) {
+        super.postValue(value);
+    }
+
+    @Override
+    public void setValue(T value) {
+        super.setValue(value);
+    }
+}
diff --git a/lifecycle/extensions/src/main/java/android/arch/lifecycle/Observer.java b/lifecycle/extensions/src/main/java/android/arch/lifecycle/Observer.java
new file mode 100644
index 0000000..0e36775
--- /dev/null
+++ b/lifecycle/extensions/src/main/java/android/arch/lifecycle/Observer.java
@@ -0,0 +1,34 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.arch.lifecycle;
+
+import android.support.annotation.Nullable;
+
+/**
+ * A simple callback that can receive from {@link LiveData}.
+ *
+ * @param <T> The type of the parameter
+ *
+ * @see LiveData LiveData - for a usage description.
+ */
+public interface Observer<T> {
+    /**
+     * Called when the data is changed.
+     * @param t  The new data
+     */
+    void onChanged(@Nullable T t);
+}
diff --git a/lifecycle/extensions/src/main/java/android/arch/lifecycle/ProcessLifecycleOwner.java b/lifecycle/extensions/src/main/java/android/arch/lifecycle/ProcessLifecycleOwner.java
new file mode 100644
index 0000000..e2a1256
--- /dev/null
+++ b/lifecycle/extensions/src/main/java/android/arch/lifecycle/ProcessLifecycleOwner.java
@@ -0,0 +1,178 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.arch.lifecycle;
+
+import android.app.Activity;
+import android.app.Application;
+import android.arch.lifecycle.ReportFragment.ActivityInitializationListener;
+import android.content.Context;
+import android.os.Bundle;
+import android.os.Handler;
+import android.support.annotation.VisibleForTesting;
+
+/**
+ * Class that provides lifecycle for the whole application process.
+ * <p>
+ * You can consider this LifecycleOwner as the composite of all of your Activities, except that
+ * {@link Lifecycle.Event#ON_CREATE} will be dispatched once and {@link Lifecycle.Event#ON_DESTROY}
+ * will never be dispatched. Other lifecycle events will be dispatched with following rules:
+ * ProcessLifecycleOwner will dispatch {@link Lifecycle.Event#ON_START},
+ * {@link Lifecycle.Event#ON_RESUME} events, as a first activity moves through these events.
+ * {@link Lifecycle.Event#ON_PAUSE}, {@link Lifecycle.Event#ON_STOP}, events will be dispatched with
+ * a <b>delay</b> after a last activity
+ * passed through them. This delay is long enough to guarantee that ProcessLifecycleOwner
+ * won't send any events if activities are destroyed and recreated due to a
+ * configuration change.
+ *
+ * <p>
+ * It is useful for use cases where you would like to react on your app coming to the foreground or
+ * going to the background and you don't need a milliseconds accuracy in receiving lifecycle
+ * events.
+ */
+@SuppressWarnings("WeakerAccess")
+public class ProcessLifecycleOwner implements LifecycleOwner {
+
+    @VisibleForTesting
+    static final long TIMEOUT_MS = 700; //mls
+
+    // ground truth counters
+    private int mStartedCounter = 0;
+    private int mResumedCounter = 0;
+
+    private boolean mPauseSent = true;
+    private boolean mStopSent = true;
+
+    private Handler mHandler;
+    private final LifecycleRegistry mRegistry = new LifecycleRegistry(this);
+
+    private Runnable mDelayedPauseRunnable = new Runnable() {
+        @Override
+        public void run() {
+            dispatchPauseIfNeeded();
+            dispatchStopIfNeeded();
+        }
+    };
+
+    private ActivityInitializationListener mInitializationListener =
+            new ActivityInitializationListener() {
+                @Override
+                public void onCreate() {
+                }
+
+                @Override
+                public void onStart() {
+                    activityStarted();
+                }
+
+                @Override
+                public void onResume() {
+                    activityResumed();
+                }
+            };
+
+    private static final ProcessLifecycleOwner sInstance = new ProcessLifecycleOwner();
+
+    /**
+     * The LifecycleOwner for the whole application process. Note that if your application
+     * has multiple processes, this provider does not know about other processes.
+     *
+     * @return {@link LifecycleOwner} for the whole application.
+     */
+    public static LifecycleOwner get() {
+        return sInstance;
+    }
+
+    static void init(Context context) {
+        sInstance.attach(context);
+    }
+
+    void activityStarted() {
+        mStartedCounter++;
+        if (mStartedCounter == 1 && mStopSent) {
+            mRegistry.handleLifecycleEvent(Lifecycle.Event.ON_START);
+            mStopSent = false;
+        }
+    }
+
+    void activityResumed() {
+        mResumedCounter++;
+        if (mResumedCounter == 1) {
+            if (mPauseSent) {
+                mRegistry.handleLifecycleEvent(Lifecycle.Event.ON_RESUME);
+                mPauseSent = false;
+            } else {
+                mHandler.removeCallbacks(mDelayedPauseRunnable);
+            }
+        }
+    }
+
+    void activityPaused() {
+        mResumedCounter--;
+        if (mResumedCounter == 0) {
+            mHandler.postDelayed(mDelayedPauseRunnable, TIMEOUT_MS);
+        }
+    }
+
+    void activityStopped() {
+        mStartedCounter--;
+        dispatchStopIfNeeded();
+    }
+
+    private void dispatchPauseIfNeeded() {
+        if (mResumedCounter == 0) {
+            mPauseSent = true;
+            mRegistry.handleLifecycleEvent(Lifecycle.Event.ON_PAUSE);
+        }
+    }
+
+    private void dispatchStopIfNeeded() {
+        if (mStartedCounter == 0 && mPauseSent) {
+            mRegistry.handleLifecycleEvent(Lifecycle.Event.ON_STOP);
+            mStopSent = true;
+        }
+    }
+
+    private ProcessLifecycleOwner() {
+    }
+
+    void attach(Context context) {
+        mHandler = new Handler();
+        mRegistry.handleLifecycleEvent(Lifecycle.Event.ON_CREATE);
+        Application app = (Application) context.getApplicationContext();
+        app.registerActivityLifecycleCallbacks(new EmptyActivityLifecycleCallbacks() {
+            @Override
+            public void onActivityCreated(Activity activity, Bundle savedInstanceState) {
+                ReportFragment  .get(activity).setProcessListener(mInitializationListener);
+            }
+
+            @Override
+            public void onActivityPaused(Activity activity) {
+                activityPaused();
+            }
+
+            @Override
+            public void onActivityStopped(Activity activity) {
+                activityStopped();
+            }
+        });
+    }
+
+    @Override
+    public Lifecycle getLifecycle() {
+        return mRegistry;
+    }
+}
diff --git a/lifecycle/extensions/src/main/java/android/arch/lifecycle/ServiceLifecycleDispatcher.java b/lifecycle/extensions/src/main/java/android/arch/lifecycle/ServiceLifecycleDispatcher.java
new file mode 100644
index 0000000..6067d7b
--- /dev/null
+++ b/lifecycle/extensions/src/main/java/android/arch/lifecycle/ServiceLifecycleDispatcher.java
@@ -0,0 +1,108 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.arch.lifecycle;
+
+import android.app.Service;
+import android.content.Intent;
+import android.os.Handler;
+import android.support.annotation.NonNull;
+
+/**
+ * Helper class to dispatch lifecycle events for a service. Use it only if it is impossible
+ * to use {@link LifecycleService}.
+ */
+@SuppressWarnings("WeakerAccess")
+public class ServiceLifecycleDispatcher {
+    private final LifecycleRegistry mRegistry;
+    private final Handler mHandler;
+    private DispatchRunnable mLastDispatchRunnable;
+
+    /**
+     * @param provider {@link LifecycleOwner} for a service, usually it is a service itself
+     */
+    public ServiceLifecycleDispatcher(@NonNull LifecycleOwner provider) {
+        mRegistry = new LifecycleRegistry(provider);
+        mHandler = new Handler();
+    }
+
+    private void postDispatchRunnable(Lifecycle.Event event) {
+        if (mLastDispatchRunnable != null) {
+            mLastDispatchRunnable.run();
+        }
+        mLastDispatchRunnable = new DispatchRunnable(mRegistry, event);
+        mHandler.postAtFrontOfQueue(mLastDispatchRunnable);
+    }
+
+    /**
+     * Must be a first call in {@link Service#onCreate()} method, even before super.onCreate call.
+     */
+    public void onServicePreSuperOnCreate() {
+        postDispatchRunnable(Lifecycle.Event.ON_CREATE);
+    }
+
+    /**
+     * Must be a first call in {@link Service#onBind(Intent)} method, even before super.onBind
+     * call.
+     */
+    public void onServicePreSuperOnBind() {
+        postDispatchRunnable(Lifecycle.Event.ON_START);
+    }
+
+    /**
+     * Must be a first call in {@link Service#onStart(Intent, int)} or
+     * {@link Service#onStartCommand(Intent, int, int)} methods, even before
+     * a corresponding super call.
+     */
+    public void onServicePreSuperOnStart() {
+        postDispatchRunnable(Lifecycle.Event.ON_START);
+    }
+
+    /**
+     * Must be a first call in {@link Service#onDestroy()} method, even before super.OnDestroy
+     * call.
+     */
+    public void onServicePreSuperOnDestroy() {
+        postDispatchRunnable(Lifecycle.Event.ON_STOP);
+        postDispatchRunnable(Lifecycle.Event.ON_DESTROY);
+    }
+
+    /**
+     * @return {@link Lifecycle} for the given {@link LifecycleOwner}
+     */
+    public Lifecycle getLifecycle() {
+        return mRegistry;
+    }
+
+    static class DispatchRunnable implements Runnable {
+        private final LifecycleRegistry mRegistry;
+        final Lifecycle.Event mEvent;
+        private boolean mWasExecuted = false;
+
+        DispatchRunnable(@NonNull LifecycleRegistry registry, Lifecycle.Event event) {
+            mRegistry = registry;
+            mEvent = event;
+        }
+
+        @Override
+        public void run() {
+            if (!mWasExecuted) {
+                mRegistry.handleLifecycleEvent(mEvent);
+                mWasExecuted = true;
+            }
+        }
+    }
+}
diff --git a/lifecycle/extensions/src/main/java/android/arch/lifecycle/Transformations.java b/lifecycle/extensions/src/main/java/android/arch/lifecycle/Transformations.java
new file mode 100644
index 0000000..c316563
--- /dev/null
+++ b/lifecycle/extensions/src/main/java/android/arch/lifecycle/Transformations.java
@@ -0,0 +1,103 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.arch.lifecycle;
+
+import android.arch.core.util.Function;
+import android.support.annotation.MainThread;
+import android.support.annotation.Nullable;
+
+/**
+ * Transformations for a {@link LiveData} class.
+ */
+@SuppressWarnings("WeakerAccess")
+public class Transformations {
+
+    private Transformations() {
+    }
+
+    /**
+     * Applies the given function on the main thread to each value emitted by {@code source}
+     * LiveData and returns LiveData, which emits resulting values.
+     * <p>
+     * The given function {@code func} will be executed on the main thread.
+     *
+     * @param source a {@code LiveData} to listen to
+     * @param func   a function to apply
+     * @param <X>    a type of {@code source} LiveData
+     * @param <Y>    a type of resulting LiveData.
+     * @return a LiveData which emits resulting values
+     */
+    @MainThread
+    public static <X, Y> LiveData<Y> map(LiveData<X> source, final Function<X, Y> func) {
+        final MediatorLiveData<Y> result = new MediatorLiveData<>();
+        result.addSource(source, new Observer<X>() {
+            @Override
+            public void onChanged(@Nullable X x) {
+                result.setValue(func.apply(x));
+            }
+        });
+        return result;
+    }
+
+    /**
+     * Creates a LiveData, let's name it {@code swLiveData}, which follows next flow:
+     * it reacts on changes of {@code trigger} LiveData, applies the given function to new value of
+     * {@code trigger} LiveData and sets resulting LiveData as a "backing" LiveData
+     * to {@code swLiveData}.
+     * "Backing" LiveData means, that all events emitted by it will retransmitted
+     * by {@code swLiveData}.
+     * <p>
+     * If the given function returns null, then {@code swLiveData} is not "backed" by any other
+     * LiveData.
+     * <p>
+     * The given function {@code func} will be executed on the main thread.
+     *
+     * @param trigger a {@code LiveData} to listen to
+     * @param func    a function which creates "backing" LiveData
+     * @param <X>     a type of {@code source} LiveData
+     * @param <Y>     a type of resulting LiveData
+     */
+    @MainThread
+    public static <X, Y> LiveData<Y> switchMap(LiveData<X> trigger,
+            final Function<X, LiveData<Y>> func) {
+        final MediatorLiveData<Y> result = new MediatorLiveData<>();
+        result.addSource(trigger, new Observer<X>() {
+            LiveData<Y> mSource;
+
+            @Override
+            public void onChanged(@Nullable X x) {
+                LiveData<Y> newLiveData = func.apply(x);
+                if (mSource == newLiveData) {
+                    return;
+                }
+                if (mSource != null) {
+                    result.removeSource(mSource);
+                }
+                mSource = newLiveData;
+                if (mSource != null) {
+                    result.addSource(mSource, new Observer<Y>() {
+                        @Override
+                        public void onChanged(@Nullable Y y) {
+                            result.setValue(y);
+                        }
+                    });
+                }
+            }
+        });
+        return result;
+    }
+}
diff --git a/lifecycle/extensions/src/main/java/android/arch/lifecycle/ViewModel.java b/lifecycle/extensions/src/main/java/android/arch/lifecycle/ViewModel.java
new file mode 100644
index 0000000..0310c46
--- /dev/null
+++ b/lifecycle/extensions/src/main/java/android/arch/lifecycle/ViewModel.java
@@ -0,0 +1,106 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.arch.lifecycle;
+
+/**
+ * ViewModel is a class that is responsible for preparing and managing the data for
+ * an {@link android.app.Activity Activity} or a {@link android.support.v4.app.Fragment Fragment}.
+ * It also handles the communication of the Activity / Fragment with the rest of the application
+ * (e.g. calling the business logic classes).
+ * <p>
+ * A ViewModel is always created in association with a scope (an fragment or an activity) and will
+ * be retained as long as the scope is alive. E.g. if it is an Activity, until it is
+ * finished.
+ * <p>
+ * In other words, this means that a ViewModel will not be destroyed if its owner is destroyed for a
+ * configuration change (e.g. rotation). The new instance of the owner will just re-connected to the
+ * existing ViewModel.
+ * <p>
+ * The purpose of the ViewModel is to acquire and keep the information that is necessary for an
+ * Activity or a Fragment. The Activity or the Fragment should be able to observe changes in the
+ * ViewModel. ViewModels usually expose this information via {@link LiveData} or Android Data
+ * Binding. You can also use any observability construct from you favorite framework.
+ * <p>
+ * ViewModel's only responsibility is to manage the data for the UI. It <b>should never</b> access
+ * your view hierarchy or hold a reference back to the Activity or the Fragment.
+ * <p>
+ * Typical usage from an Activity standpoint would be:
+ * <pre>
+ * public class UserActivity extends Activity {
+ *
+ *     {@literal @}Override
+ *     protected void onCreate(Bundle savedInstanceState) {
+ *         super.onCreate(savedInstanceState);
+ *         setContentView(R.layout.user_activity_layout);
+ *         final UserModel viewModel = ViewModelProviders.of(this).get(UserModel.class);
+ *         viewModel.userLiveData.observer(this, new Observer<User>() {
+ *            {@literal @}Override
+ *             public void onChanged(@Nullable User data) {
+ *                 // update ui.
+ *             }
+ *         });
+ *         findViewById(R.id.button).setOnClickListener(new View.OnClickListener() {
+ *             {@literal @}Override
+ *             public void onClick(View v) {
+ *                  viewModel.doAction();
+ *             }
+ *         });
+ *     }
+ * }
+ * </pre>
+ *
+ * ViewModel would be:
+ * <pre>
+ * public class UserModel extends ViewModel {
+ *     public final LiveData&lt;User&gt; userLiveData = new LiveData<>();
+ *
+ *     public UserModel() {
+ *         // trigger user load.
+ *     }
+ *
+ *     void doAction() {
+ *         // depending on the action, do necessary business logic calls and update the
+ *         // userLiveData.
+ *     }
+ * }
+ * </pre>
+ *
+ * <p>
+ * ViewModels can also be used as a communication layer between different Fragments of an Activity.
+ * Each Fragment can acquire the ViewModel using the same key via their Activity. This allows
+ * communication between Fragments in a de-coupled fashion such that they never need to talk to
+ * the other Fragment directly.
+ * <pre>
+ * public class MyFragment extends Fragment {
+ *     public void onStart() {
+ *         UserModel userModel = ViewModelProviders.of(getActivity()).get(UserModel.class);
+ *     }
+ * }
+ * </pre>
+ * </>
+ */
+public abstract class ViewModel {
+    /**
+     * This method will be called when this ViewModel is no longer used and will be destroyed.
+     * <p>
+     * It is useful when ViewModel observes some data and you need to clear this subscription to
+     * prevent a leak of this ViewModel.
+     */
+    @SuppressWarnings("WeakerAccess")
+    protected void onCleared() {
+    }
+}
diff --git a/lifecycle/extensions/src/main/java/android/arch/lifecycle/ViewModelProvider.java b/lifecycle/extensions/src/main/java/android/arch/lifecycle/ViewModelProvider.java
new file mode 100644
index 0000000..7ef591f
--- /dev/null
+++ b/lifecycle/extensions/src/main/java/android/arch/lifecycle/ViewModelProvider.java
@@ -0,0 +1,151 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.arch.lifecycle;
+
+import android.support.annotation.MainThread;
+import android.support.annotation.NonNull;
+
+/**
+ * An utility class that provides {@code ViewModels} for a scope.
+ * <p>
+ * Default {@code ViewModelProvider} for an {@code Activity} or a {@code Fragment} can be obtained
+ * from {@link android.arch.lifecycle.ViewModelProviders} class.
+ */
+@SuppressWarnings("WeakerAccess")
+public class ViewModelProvider {
+
+    private static final String DEFAULT_KEY =
+            "android.arch.lifecycle.ViewModelProvider.DefaultKey";
+
+    /**
+     * Implementations of {@code Factory} interface are responsible to instantiate ViewModels.
+     */
+    public interface Factory {
+        /**
+         * Creates a new instance of the given {@code Class}.
+         * <p>
+         *
+         * @param modelClass a {@code Class} whose instance is requested
+         * @param <T>        The type parameter for the ViewModel.
+         * @return a newly created ViewModel
+         */
+        <T extends ViewModel> T create(Class<T> modelClass);
+    }
+
+    private final Factory mFactory;
+    private final ViewModelStore mViewModelStore;
+
+    /**
+     * Creates {@code ViewModelProvider}, which will create {@code ViewModels} via the given
+     * {@code Factory} and retain them in a store of the given {@code ViewModelStoreOwner}.
+     *
+     * @param owner   a {@code ViewModelStoreOwner} whose {@link ViewModelStore} will be used to
+     *                retain {@code ViewModels}
+     * @param factory a {@code Factory} which will be used to instantiate
+     *                new {@code ViewModels}
+     */
+    public ViewModelProvider(@NonNull ViewModelStoreOwner owner, @NonNull Factory factory) {
+        this(owner.getViewModelStore(), factory);
+    }
+
+    /**
+     * Creates {@code ViewModelProvider}, which will create {@code ViewModels} via the given
+     * {@code Factory} and retain them in the given {@code store}.
+     *
+     * @param store   {@code ViewModelStore} where ViewModels will be stored.
+     * @param factory factory a {@code Factory} which will be used to instantiate
+     *                new {@code ViewModels}
+     */
+    public ViewModelProvider(ViewModelStore store, Factory factory) {
+        mFactory = factory;
+        this.mViewModelStore = store;
+    }
+
+    /**
+     * Returns an existing ViewModel or creates a new one in the scope (usually, a fragment or
+     * an activity), associated with this {@code ViewModelProvider}.
+     * <p>
+     * The created ViewModel is associated with the given scope and will be retained
+     * as long as the scope is alive (e.g. if it is an activity, until it is
+     * finished or process is killed).
+     *
+     * @param modelClass The class of the ViewModel to create an instance of it if it is not
+     *                   present.
+     * @param <T>        The type parameter for the ViewModel.
+     * @return A ViewModel that is an instance of the given type {@code T}.
+     */
+    public <T extends ViewModel> T get(Class<T> modelClass) {
+        String canonicalName = modelClass.getCanonicalName();
+        if (canonicalName == null) {
+            throw new IllegalArgumentException("Local and anonymous classes can not be ViewModels");
+        }
+        return get(DEFAULT_KEY + ":" + canonicalName, modelClass);
+    }
+
+    /**
+     * Returns an existing ViewModel or creates a new one in the scope (usually, a fragment or
+     * an activity), associated with this {@code ViewModelProvider}.
+     * <p>
+     * The created ViewModel is associated with the given scope and will be retained
+     * as long as the scope is alive (e.g. if it is an activity, until it is
+     * finished or process is killed).
+     *
+     * @param key        The key to use to identify the ViewModel.
+     * @param modelClass The class of the ViewModel to create an instance of it if it is not
+     *                   present.
+     * @param <T>        The type parameter for the ViewModel.
+     * @return A ViewModel that is an instance of the given type {@code T}.
+     */
+    @NonNull
+    @MainThread
+    public <T extends ViewModel> T get(@NonNull String key, @NonNull Class<T> modelClass) {
+        ViewModel viewModel = mViewModelStore.get(key);
+
+        if (modelClass.isInstance(viewModel)) {
+            //noinspection unchecked
+            return (T) viewModel;
+        } else {
+            //noinspection StatementWithEmptyBody
+            if (viewModel != null) {
+                // TODO: log a warning.
+            }
+        }
+
+        viewModel = mFactory.create(modelClass);
+        mViewModelStore.put(key, viewModel);
+        //noinspection unchecked
+        return (T) viewModel;
+    }
+
+    /**
+     * Simple factory, which calls empty constructor on the give class.
+     */
+    public static class NewInstanceFactory implements Factory {
+
+        @Override
+        public <T extends ViewModel> T create(Class<T> modelClass) {
+            //noinspection TryWithIdenticalCatches
+            try {
+                return modelClass.newInstance();
+            } catch (InstantiationException e) {
+                throw new RuntimeException("Cannot create an instance of " + modelClass, e);
+            } catch (IllegalAccessException e) {
+                throw new RuntimeException("Cannot create an instance of " + modelClass, e);
+            }
+        }
+    }
+}
diff --git a/lifecycle/extensions/src/main/java/android/arch/lifecycle/ViewModelProviders.java b/lifecycle/extensions/src/main/java/android/arch/lifecycle/ViewModelProviders.java
new file mode 100644
index 0000000..f64365b
--- /dev/null
+++ b/lifecycle/extensions/src/main/java/android/arch/lifecycle/ViewModelProviders.java
@@ -0,0 +1,146 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.arch.lifecycle;
+
+import android.annotation.SuppressLint;
+import android.app.Application;
+import android.arch.lifecycle.ViewModelProvider.Factory;
+import android.support.annotation.MainThread;
+import android.support.annotation.NonNull;
+import android.support.v4.app.Fragment;
+import android.support.v4.app.FragmentActivity;
+
+import java.lang.reflect.InvocationTargetException;
+
+/**
+ * Utilities methods for {@link ViewModelStore} class.
+ */
+public class ViewModelProviders {
+
+    @SuppressLint("StaticFieldLeak")
+    private static DefaultFactory sDefaultFactory;
+
+    private static void initializeFactoryIfNeeded(Application application) {
+        if (sDefaultFactory == null) {
+            sDefaultFactory = new DefaultFactory(application);
+        }
+    }
+
+    /**
+     * Creates a {@link ViewModelProvider}, which retains ViewModels while a scope of given
+     * {@code fragment} is alive. More detailed explanation is in {@link ViewModel}.
+     * <p>
+     * It uses {@link DefaultFactory} to instantiate new ViewModels.
+     *
+     * @param fragment a fragment, in whose scope ViewModels should be retained
+     * @return a ViewModelProvider instance
+     */
+    @MainThread
+    public static ViewModelProvider of(@NonNull Fragment fragment) {
+        FragmentActivity activity = fragment.getActivity();
+        if (activity == null) {
+            throw new IllegalArgumentException(
+                    "Can't create ViewModelProvider for detached fragment");
+        }
+        initializeFactoryIfNeeded(activity.getApplication());
+        return new ViewModelProvider(ViewModelStores.of(fragment), sDefaultFactory);
+    }
+
+    /**
+     * Creates a {@link ViewModelProvider}, which retains ViewModels while a scope of given Activity
+     * is alive. More detailed explanation is in {@link ViewModel}.
+     * <p>
+     * It uses {@link DefaultFactory} to instantiate new ViewModels.
+     *
+     * @param activity an activity, in whose scope ViewModels should be retained
+     * @return a ViewModelProvider instance
+     */
+    @MainThread
+    public static ViewModelProvider of(@NonNull FragmentActivity activity) {
+        initializeFactoryIfNeeded(activity.getApplication());
+        return new ViewModelProvider(ViewModelStores.of(activity), sDefaultFactory);
+    }
+
+    /**
+     * Creates a {@link ViewModelProvider}, which retains ViewModels while a scope of given
+     * {@code fragment} is alive. More detailed explanation is in {@link ViewModel}.
+     * <p>
+     * It uses the given {@link Factory} to instantiate new ViewModels.
+     *
+     * @param fragment a fragment, in whose scope ViewModels should be retained
+     * @param factory  a {@code Factory} to instantiate new ViewModels
+     * @return a ViewModelProvider instance
+     */
+    @MainThread
+    public static ViewModelProvider of(@NonNull Fragment fragment, @NonNull Factory factory) {
+        return new ViewModelProvider(ViewModelStores.of(fragment), factory);
+    }
+
+    /**
+     * Creates a {@link ViewModelProvider}, which retains ViewModels while a scope of given Activity
+     * is alive. More detailed explanation is in {@link ViewModel}.
+     * <p>
+     * It uses the given {@link Factory} to instantiate new ViewModels.
+     *
+     * @param activity an activity, in whose scope ViewModels should be retained
+     * @param factory  a {@code Factory} to instantiate new ViewModels
+     * @return a ViewModelProvider instance
+     */
+    @MainThread
+    public static ViewModelProvider of(@NonNull FragmentActivity activity,
+            @NonNull Factory factory) {
+        return new ViewModelProvider(ViewModelStores.of(activity), factory);
+    }
+
+    /**
+     * {@link Factory} which may create {@link AndroidViewModel} and
+     * {@link ViewModel}, which have an empty constructor.
+     */
+    @SuppressWarnings("WeakerAccess")
+    public static class DefaultFactory extends ViewModelProvider.NewInstanceFactory {
+
+        private Application mApplication;
+
+        /**
+         * Creates a {@code DefaultFactory}
+         *
+         * @param application an application to pass in {@link AndroidViewModel}
+         */
+        public DefaultFactory(@NonNull Application application) {
+            mApplication = application;
+        }
+
+        @Override
+        public <T extends ViewModel> T create(Class<T> modelClass) {
+            if (AndroidViewModel.class.isAssignableFrom(modelClass)) {
+                //noinspection TryWithIdenticalCatches
+                try {
+                    return modelClass.getConstructor(Application.class).newInstance(mApplication);
+                } catch (NoSuchMethodException e) {
+                    throw new RuntimeException("Cannot create an instance of " + modelClass, e);
+                } catch (IllegalAccessException e) {
+                    throw new RuntimeException("Cannot create an instance of " + modelClass, e);
+                } catch (InstantiationException e) {
+                    throw new RuntimeException("Cannot create an instance of " + modelClass, e);
+                } catch (InvocationTargetException e) {
+                    throw new RuntimeException("Cannot create an instance of " + modelClass, e);
+                }
+            }
+            return super.create(modelClass);
+        }
+    }
+}
diff --git a/lifecycle/extensions/src/main/java/android/arch/lifecycle/ViewModelStore.java b/lifecycle/extensions/src/main/java/android/arch/lifecycle/ViewModelStore.java
new file mode 100644
index 0000000..a1864bb
--- /dev/null
+++ b/lifecycle/extensions/src/main/java/android/arch/lifecycle/ViewModelStore.java
@@ -0,0 +1,61 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.arch.lifecycle;
+
+import java.util.HashMap;
+
+/**
+ * Class to store {@code ViewModels}.
+ * <p>
+ * An instance of {@code ViewModelStore} must be retained through configuration changes:
+ * if an owner of this {@code ViewModelStore} is destroyed and recreated due to configuration
+ * changes, new instance of an owner should still have the same old instance of
+ * {@code ViewModelStore}.
+ * <p>
+ * If an owner of this {@code ViewModelStore} is destroyed and is not going to be recreated,
+ * then it should call {@link #clear()} on this {@code ViewModelStore}, so {@code ViewModels} would
+ * be notified that they are no longer used.
+ * <p>
+ * {@link android.arch.lifecycle.ViewModelStores} provides a {@code ViewModelStore} for
+ * activities and fragments.
+ */
+public class ViewModelStore {
+
+    private final HashMap<String, ViewModel> mMap = new HashMap<>();
+
+    final void put(String key, ViewModel viewModel) {
+        ViewModel oldViewModel = mMap.get(key);
+        if (oldViewModel != null) {
+            oldViewModel.onCleared();
+        }
+        mMap.put(key, viewModel);
+    }
+
+    final ViewModel get(String key) {
+        return mMap.get(key);
+    }
+
+    /**
+     *  Clears internal storage and notifies ViewModels that they are no longer used.
+     */
+    public final void clear() {
+        for (ViewModel vm : mMap.values()) {
+            vm.onCleared();
+        }
+        mMap.clear();
+    }
+}
diff --git a/lifecycle/extensions/src/main/java/android/arch/lifecycle/ViewModelStoreOwner.java b/lifecycle/extensions/src/main/java/android/arch/lifecycle/ViewModelStoreOwner.java
new file mode 100644
index 0000000..5058305
--- /dev/null
+++ b/lifecycle/extensions/src/main/java/android/arch/lifecycle/ViewModelStoreOwner.java
@@ -0,0 +1,34 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.arch.lifecycle;
+
+/**
+ * A scope that owns {@link ViewModelStore}.
+ * <p>
+ * A responsibility of an implementation of this interface is to retain owned ViewModelStore
+ * during the configuration changes and call {@link ViewModelStore#clear()}, when this scope is
+ * going to be destroyed.
+ */
+@SuppressWarnings("WeakerAccess")
+public interface ViewModelStoreOwner {
+    /**
+     * Returns owned {@link ViewModelStore}
+     *
+     * @return a {@code ViewModelStore}
+     */
+    ViewModelStore getViewModelStore();
+}
diff --git a/lifecycle/extensions/src/main/java/android/arch/lifecycle/ViewModelStores.java b/lifecycle/extensions/src/main/java/android/arch/lifecycle/ViewModelStores.java
new file mode 100644
index 0000000..8c17dd9
--- /dev/null
+++ b/lifecycle/extensions/src/main/java/android/arch/lifecycle/ViewModelStores.java
@@ -0,0 +1,55 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.arch.lifecycle;
+
+import static android.arch.lifecycle.HolderFragment.holderFragmentFor;
+
+import android.support.annotation.MainThread;
+import android.support.v4.app.Fragment;
+import android.support.v4.app.FragmentActivity;
+
+/**
+ * Factory methods for {@link ViewModelStore} class.
+ */
+@SuppressWarnings("WeakerAccess")
+public class ViewModelStores {
+
+    private ViewModelStores() {
+    }
+
+    /**
+     * Returns the {@link ViewModelStore} of the given activity.
+     *
+     * @param activity an activity whose {@code ViewModelStore} is requested
+     * @return a {@code ViewModelStore}
+     */
+    @MainThread
+    public static ViewModelStore of(FragmentActivity activity) {
+        return holderFragmentFor(activity).getViewModelStore();
+    }
+
+    /**
+     * Returns the {@link ViewModelStore} of the given fragment.
+     *
+     * @param fragment a fragment whose {@code ViewModelStore} is requested
+     * @return a {@code ViewModelStore}
+     */
+    @MainThread
+    public static ViewModelStore of(Fragment fragment) {
+        return holderFragmentFor(fragment).getViewModelStore();
+    }
+}
diff --git a/lifecycle/extensions/src/test/java/android/arch/lifecycle/ComputableLiveDataTest.java b/lifecycle/extensions/src/test/java/android/arch/lifecycle/ComputableLiveDataTest.java
new file mode 100644
index 0000000..0a3fbed
--- /dev/null
+++ b/lifecycle/extensions/src/test/java/android/arch/lifecycle/ComputableLiveDataTest.java
@@ -0,0 +1,215 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.arch.lifecycle;
+
+import static org.hamcrest.CoreMatchers.is;
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.mockito.Matchers.any;
+import static org.mockito.Matchers.anyInt;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.reset;
+import static org.mockito.Mockito.spy;
+import static org.mockito.Mockito.timeout;
+import static org.mockito.Mockito.verify;
+
+import android.arch.core.executor.AppToolkitTaskExecutor;
+import android.arch.core.executor.TaskExecutor;
+import android.arch.core.executor.TaskExecutorWithFakeMainThread;
+import android.arch.lifecycle.util.InstantTaskExecutor;
+import android.support.annotation.Nullable;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+import org.mockito.ArgumentCaptor;
+
+import java.util.Collections;
+import java.util.concurrent.Semaphore;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.atomic.AtomicInteger;
+
+@RunWith(JUnit4.class)
+public class ComputableLiveDataTest {
+    private TaskExecutor mTaskExecutor;
+    private TestLifecycleOwner mLifecycleOwner;
+
+    @Before
+    public void setup() {
+        mLifecycleOwner = new TestLifecycleOwner();
+    }
+
+    @Before
+    public void swapExecutorDelegate() {
+        mTaskExecutor = spy(new InstantTaskExecutor());
+        AppToolkitTaskExecutor.getInstance().setDelegate(mTaskExecutor);
+    }
+
+    @After
+    public void removeExecutorDelegate() {
+        AppToolkitTaskExecutor.getInstance().setDelegate(null);
+    }
+
+    @Test
+    public void noComputeWithoutObservers() {
+        final TestComputable computable = new TestComputable();
+        verify(mTaskExecutor, never()).executeOnDiskIO(computable.mRefreshRunnable);
+        verify(mTaskExecutor, never()).executeOnDiskIO(computable.mInvalidationRunnable);
+    }
+
+    @Test
+    public void noConcurrentCompute() throws InterruptedException {
+        TaskExecutorWithFakeMainThread executor = new TaskExecutorWithFakeMainThread(2);
+        AppToolkitTaskExecutor.getInstance().setDelegate(executor);
+        try {
+            // # of compute calls
+            final Semaphore computeCounter = new Semaphore(0);
+            // available permits for computation
+            final Semaphore computeLock = new Semaphore(0);
+            final TestComputable computable = new TestComputable(1, 2) {
+                @Override
+                protected Integer compute() {
+                    try {
+                        computeCounter.release(1);
+                        computeLock.tryAcquire(1, 20, TimeUnit.SECONDS);
+                    } catch (InterruptedException e) {
+                        throw new AssertionError(e);
+                    }
+                    return super.compute();
+                }
+            };
+            final ArgumentCaptor<Integer> captor = ArgumentCaptor.forClass(Integer.class);
+            //noinspection unchecked
+            Observer<Integer> observer = mock(Observer.class);
+            computable.getLiveData().observeForever(observer);
+            verify(observer, never()).onChanged(anyInt());
+            // wait for first compute call
+            assertThat(computeCounter.tryAcquire(1, 2, TimeUnit.SECONDS), is(true));
+            // re-invalidate while in compute
+            computable.invalidate();
+            computable.invalidate();
+            computable.invalidate();
+            computable.invalidate();
+            // ensure another compute call does not arrive
+            assertThat(computeCounter.tryAcquire(1, 2, TimeUnit.SECONDS), is(false));
+            // allow computation to finish
+            computeLock.release(2);
+            // wait for the second result, first will be skipped due to invalidation during compute
+            verify(observer, timeout(2000)).onChanged(captor.capture());
+            assertThat(captor.getAllValues(), is(Collections.singletonList(2)));
+            reset(observer);
+            // allow all computations to run, there should not be any.
+            computeLock.release(100);
+            // unfortunately, Mockito.after is not available in 1.9.5
+            executor.drainTasks(2);
+            // assert no other results arrive
+            verify(observer, never()).onChanged(anyInt());
+        } finally {
+            AppToolkitTaskExecutor.getInstance().setDelegate(null);
+        }
+    }
+
+    @Test
+    public void addingObserverShouldTriggerAComputation() {
+        TestComputable computable = new TestComputable(1);
+        mLifecycleOwner.handleEvent(Lifecycle.Event.ON_CREATE);
+        final AtomicInteger mValue = new AtomicInteger(-1);
+        computable.getLiveData().observe(mLifecycleOwner, new Observer<Integer>() {
+            @Override
+            public void onChanged(@Nullable Integer integer) {
+                //noinspection ConstantConditions
+                mValue.set(integer);
+            }
+        });
+        verify(mTaskExecutor, never()).executeOnDiskIO(any(Runnable.class));
+        assertThat(mValue.get(), is(-1));
+        mLifecycleOwner.handleEvent(Lifecycle.Event.ON_START);
+        verify(mTaskExecutor).executeOnDiskIO(computable.mRefreshRunnable);
+        assertThat(mValue.get(), is(1));
+    }
+
+    @Test
+    public void invalidationShouldNotReTriggerComputationIfObserverIsInActive() {
+        TestComputable computable = new TestComputable(1, 2);
+        mLifecycleOwner.handleEvent(Lifecycle.Event.ON_START);
+        final AtomicInteger mValue = new AtomicInteger(-1);
+        computable.getLiveData().observe(mLifecycleOwner, new Observer<Integer>() {
+            @Override
+            public void onChanged(@Nullable Integer integer) {
+                //noinspection ConstantConditions
+                mValue.set(integer);
+            }
+        });
+        assertThat(mValue.get(), is(1));
+        mLifecycleOwner.handleEvent(Lifecycle.Event.ON_STOP);
+        computable.invalidate();
+        reset(mTaskExecutor);
+        verify(mTaskExecutor, never()).executeOnDiskIO(computable.mRefreshRunnable);
+        assertThat(mValue.get(), is(1));
+    }
+
+    @Test
+    public void invalidationShouldReTriggerQueryIfObserverIsActive() {
+        TestComputable computable = new TestComputable(1, 2);
+        mLifecycleOwner.handleEvent(Lifecycle.Event.ON_START);
+        final AtomicInteger mValue = new AtomicInteger(-1);
+        computable.getLiveData().observe(mLifecycleOwner, new Observer<Integer>() {
+            @Override
+            public void onChanged(@Nullable Integer integer) {
+                //noinspection ConstantConditions
+                mValue.set(integer);
+            }
+        });
+        assertThat(mValue.get(), is(1));
+        computable.invalidate();
+        assertThat(mValue.get(), is(2));
+    }
+
+    static class TestComputable extends ComputableLiveData<Integer> {
+        final int[] mValues;
+        AtomicInteger mValueCounter;
+
+        TestComputable(int... values) {
+            mValueCounter = new AtomicInteger();
+            mValues = values;
+        }
+
+        @Override
+        protected Integer compute() {
+            return mValues[mValueCounter.getAndIncrement()];
+        }
+    }
+
+    static class TestLifecycleOwner implements LifecycleOwner {
+        private LifecycleRegistry mLifecycle;
+
+        TestLifecycleOwner() {
+            mLifecycle = new LifecycleRegistry(this);
+        }
+
+        @Override
+        public Lifecycle getLifecycle() {
+            return mLifecycle;
+        }
+
+        void handleEvent(Lifecycle.Event event) {
+            mLifecycle.handleLifecycleEvent(event);
+        }
+    }
+}
diff --git a/lifecycle/extensions/src/test/java/android/arch/lifecycle/DispatcherActivityCallbackTest.java b/lifecycle/extensions/src/test/java/android/arch/lifecycle/DispatcherActivityCallbackTest.java
new file mode 100644
index 0000000..1431089
--- /dev/null
+++ b/lifecycle/extensions/src/test/java/android/arch/lifecycle/DispatcherActivityCallbackTest.java
@@ -0,0 +1,79 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.arch.lifecycle;
+
+import static org.mockito.Matchers.any;
+import static org.mockito.Matchers.anyString;
+import static org.mockito.Matchers.eq;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import android.annotation.SuppressLint;
+import android.app.Activity;
+import android.app.Fragment;
+import android.app.FragmentTransaction;
+import android.os.Bundle;
+import android.support.test.filters.SmallTest;
+import android.support.v4.app.FragmentActivity;
+import android.support.v4.app.FragmentManager;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+
+@RunWith(JUnit4.class)
+@SmallTest
+public class DispatcherActivityCallbackTest {
+    @Test
+    public void onCreateFrameworkActivity() {
+        LifecycleDispatcher.DispatcherActivityCallback callback =
+                new LifecycleDispatcher.DispatcherActivityCallback();
+        Activity activity = mock(Activity.class);
+        checkReportFragment(callback, activity);
+    }
+
+    @Test
+    public void onCreateFragmentActivity() {
+        LifecycleDispatcher.DispatcherActivityCallback callback =
+                new LifecycleDispatcher.DispatcherActivityCallback();
+        FragmentActivity activity = mock(FragmentActivity.class);
+        FragmentManager fragmentManager = mock(FragmentManager.class);
+        when(activity.getSupportFragmentManager()).thenReturn(fragmentManager);
+
+        checkReportFragment(callback, activity);
+
+        verify(activity).getSupportFragmentManager();
+        verify(fragmentManager).registerFragmentLifecycleCallbacks(
+                any(FragmentManager.FragmentLifecycleCallbacks.class), eq(true));
+    }
+
+    @SuppressLint("CommitTransaction")
+    private void checkReportFragment(LifecycleDispatcher.DispatcherActivityCallback callback,
+            Activity activity) {
+        android.app.FragmentManager fm = mock(android.app.FragmentManager.class);
+        FragmentTransaction transaction = mock(FragmentTransaction.class);
+        when(activity.getFragmentManager()).thenReturn(fm);
+        when(fm.beginTransaction()).thenReturn(transaction);
+        when(transaction.add(any(Fragment.class), anyString())).thenReturn(transaction);
+        callback.onActivityCreated(activity, mock(Bundle.class));
+        verify(activity).getFragmentManager();
+        verify(fm).beginTransaction();
+        verify(transaction).add(any(ReportFragment.class), anyString());
+        verify(transaction).commit();
+    }
+}
diff --git a/lifecycle/extensions/src/test/java/android/arch/lifecycle/LiveDataTest.java b/lifecycle/extensions/src/test/java/android/arch/lifecycle/LiveDataTest.java
new file mode 100644
index 0000000..f401e1c
--- /dev/null
+++ b/lifecycle/extensions/src/test/java/android/arch/lifecycle/LiveDataTest.java
@@ -0,0 +1,437 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.arch.lifecycle;
+
+import static android.arch.lifecycle.Lifecycle.Event.ON_CREATE;
+import static android.arch.lifecycle.Lifecycle.Event.ON_DESTROY;
+import static android.arch.lifecycle.Lifecycle.Event.ON_PAUSE;
+import static android.arch.lifecycle.Lifecycle.Event.ON_RESUME;
+import static android.arch.lifecycle.Lifecycle.Event.ON_START;
+import static android.arch.lifecycle.Lifecycle.Event.ON_STOP;
+
+import static org.hamcrest.CoreMatchers.instanceOf;
+import static org.hamcrest.CoreMatchers.is;
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.mockito.Matchers.anyBoolean;
+import static org.mockito.Matchers.anyString;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.reset;
+import static org.mockito.Mockito.spy;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.verifyNoMoreInteractions;
+import static org.mockito.Mockito.when;
+
+import android.arch.core.executor.AppToolkitTaskExecutor;
+import android.arch.lifecycle.util.InstantTaskExecutor;
+import android.support.annotation.Nullable;
+import android.support.test.filters.SmallTest;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.mockito.Mockito;
+
+@SuppressWarnings({"unchecked"})
+@SmallTest
+public class LiveDataTest {
+    private PublicLiveData<String> mLiveData;
+    private LifecycleOwner mOwner;
+    private LifecycleRegistry mRegistry;
+    private MethodExec mActiveObserversChanged;
+    private boolean mInObserver;
+
+    @Before
+    public void init() {
+        mLiveData = new PublicLiveData<>();
+        mOwner = mock(LifecycleOwner.class);
+        mRegistry = new LifecycleRegistry(mOwner);
+        when(mOwner.getLifecycle()).thenReturn(mRegistry);
+        mActiveObserversChanged = mock(MethodExec.class);
+        mLiveData.activeObserversChanged = mActiveObserversChanged;
+        mInObserver = false;
+    }
+
+    @Before
+    public void swapExecutorDelegate() {
+        AppToolkitTaskExecutor.getInstance().setDelegate(new InstantTaskExecutor());
+    }
+
+    @After
+    public void removeExecutorDelegate() {
+        AppToolkitTaskExecutor.getInstance().setDelegate(null);
+    }
+
+    @Test
+    public void testObserverToggle() {
+        Observer<String> observer = (Observer<String>) mock(Observer.class);
+        mLiveData.observe(mOwner, observer);
+
+        verify(mActiveObserversChanged, never()).onCall(anyBoolean());
+        assertThat(mLiveData.hasObservers(), is(true));
+        assertThat(mLiveData.hasActiveObservers(), is(false));
+
+        mLiveData.removeObserver(observer);
+        verify(mActiveObserversChanged, never()).onCall(anyBoolean());
+        assertThat(mLiveData.hasObservers(), is(false));
+        assertThat(mLiveData.hasActiveObservers(), is(false));
+    }
+
+    @Test
+    public void testActiveObserverToggle() {
+        Observer<String> observer = (Observer<String>) mock(Observer.class);
+        mLiveData.observe(mOwner, observer);
+
+        verify(mActiveObserversChanged, never()).onCall(anyBoolean());
+        assertThat(mLiveData.hasObservers(), is(true));
+        assertThat(mLiveData.hasActiveObservers(), is(false));
+
+        mRegistry.handleLifecycleEvent(ON_START);
+        verify(mActiveObserversChanged).onCall(true);
+        assertThat(mLiveData.hasActiveObservers(), is(true));
+        reset(mActiveObserversChanged);
+
+        mRegistry.handleLifecycleEvent(ON_STOP);
+        verify(mActiveObserversChanged).onCall(false);
+        assertThat(mLiveData.hasActiveObservers(), is(false));
+        assertThat(mLiveData.hasObservers(), is(true));
+
+        reset(mActiveObserversChanged);
+        mRegistry.handleLifecycleEvent(ON_START);
+        verify(mActiveObserversChanged).onCall(true);
+        assertThat(mLiveData.hasActiveObservers(), is(true));
+        assertThat(mLiveData.hasObservers(), is(true));
+
+        reset(mActiveObserversChanged);
+        mLiveData.removeObserver(observer);
+        verify(mActiveObserversChanged).onCall(false);
+        assertThat(mLiveData.hasActiveObservers(), is(false));
+        assertThat(mLiveData.hasObservers(), is(false));
+
+        verifyNoMoreInteractions(mActiveObserversChanged);
+    }
+
+    @Test
+    public void testReAddSameObserverTuple() {
+        Observer<String> observer = (Observer<String>) mock(Observer.class);
+        mLiveData.observe(mOwner, observer);
+        mLiveData.observe(mOwner, observer);
+        assertThat(mLiveData.hasObservers(), is(true));
+    }
+
+    @Test
+    public void testAdd2ObserversWithSameOwnerAndRemove() {
+        Observer<String> o1 = (Observer<String>) mock(Observer.class);
+        Observer<String> o2 = (Observer<String>) mock(Observer.class);
+        mLiveData.observe(mOwner, o1);
+        mLiveData.observe(mOwner, o2);
+        assertThat(mLiveData.hasObservers(), is(true));
+        verify(mActiveObserversChanged, never()).onCall(anyBoolean());
+
+        mRegistry.handleLifecycleEvent(ON_START);
+        verify(mActiveObserversChanged).onCall(true);
+        mLiveData.setValue("a");
+        verify(o1).onChanged("a");
+        verify(o2).onChanged("a");
+
+        mLiveData.removeObservers(mOwner);
+
+        assertThat(mLiveData.hasObservers(), is(false));
+        assertThat(mRegistry.getObserverCount(), is(0));
+    }
+
+    @Test
+    public void testAddSameObserverIn2LifecycleOwners() {
+        Observer<String> observer = (Observer<String>) mock(Observer.class);
+        LifecycleOwner owner2 = mock(LifecycleOwner.class);
+        LifecycleRegistry registry2 = new LifecycleRegistry(owner2);
+        when(owner2.getLifecycle()).thenReturn(registry2);
+
+        mLiveData.observe(mOwner, observer);
+        Throwable throwable = null;
+        try {
+            mLiveData.observe(owner2, observer);
+        } catch (Throwable t) {
+            throwable = t;
+        }
+        assertThat(throwable, instanceOf(IllegalArgumentException.class));
+        //noinspection ConstantConditions
+        assertThat(throwable.getMessage(),
+                is("Cannot add the same observer with different lifecycles"));
+    }
+
+    @Test
+    public void testRemoveDestroyedObserver() {
+        Observer<String> observer = (Observer<String>) mock(Observer.class);
+        mLiveData.observe(mOwner, observer);
+        mRegistry.handleLifecycleEvent(ON_START);
+        verify(mActiveObserversChanged).onCall(true);
+        assertThat(mLiveData.hasObservers(), is(true));
+        assertThat(mLiveData.hasActiveObservers(), is(true));
+
+        reset(mActiveObserversChanged);
+
+        mRegistry.handleLifecycleEvent(ON_DESTROY);
+        assertThat(mLiveData.hasObservers(), is(false));
+        assertThat(mLiveData.hasActiveObservers(), is(false));
+        verify(mActiveObserversChanged).onCall(false);
+    }
+
+    @Test
+    public void testInactiveRegistry() {
+        Observer<String> observer = (Observer<String>) mock(Observer.class);
+        mRegistry.handleLifecycleEvent(ON_DESTROY);
+        mLiveData.observe(mOwner, observer);
+        assertThat(mLiveData.hasObservers(), is(false));
+    }
+
+    @Test
+    public void testNotifyActiveInactive() {
+        Observer<String> observer = (Observer<String>) mock(Observer.class);
+        mRegistry.handleLifecycleEvent(ON_CREATE);
+        mLiveData.observe(mOwner, observer);
+        mLiveData.setValue("a");
+        verify(observer, never()).onChanged(anyString());
+        mRegistry.handleLifecycleEvent(ON_START);
+        verify(observer).onChanged("a");
+
+        mLiveData.setValue("b");
+        verify(observer).onChanged("b");
+
+        mRegistry.handleLifecycleEvent(ON_STOP);
+        mLiveData.setValue("c");
+        verify(observer, never()).onChanged("c");
+
+        mRegistry.handleLifecycleEvent(ON_START);
+        verify(observer).onChanged("c");
+
+        reset(observer);
+        mRegistry.handleLifecycleEvent(ON_STOP);
+        mRegistry.handleLifecycleEvent(ON_START);
+        verify(observer, never()).onChanged(anyString());
+    }
+
+    @Test
+    public void testStopObservingOwner_onDestroy() {
+        Observer<String> observer = (Observer<String>) mock(Observer.class);
+        mRegistry.handleLifecycleEvent(ON_CREATE);
+        mLiveData.observe(mOwner, observer);
+        assertThat(mRegistry.getObserverCount(), is(1));
+        mRegistry.handleLifecycleEvent(ON_DESTROY);
+        assertThat(mRegistry.getObserverCount(), is(0));
+    }
+
+    @Test
+    public void testStopObservingOwner_onStopObserving() {
+        Observer<String> observer = (Observer<String>) mock(Observer.class);
+        mRegistry.handleLifecycleEvent(ON_CREATE);
+        mLiveData.observe(mOwner, observer);
+        assertThat(mRegistry.getObserverCount(), is(1));
+
+        mLiveData.removeObserver(observer);
+        assertThat(mRegistry.getObserverCount(), is(0));
+    }
+
+    @Test
+    public void testActiveChangeInCallback() {
+        mRegistry.handleLifecycleEvent(ON_START);
+        Observer<String> observer1 = spy(new Observer<String>() {
+            @Override
+            public void onChanged(@Nullable String s) {
+                mRegistry.handleLifecycleEvent(ON_STOP);
+                assertThat(mLiveData.hasObservers(), is(true));
+                assertThat(mLiveData.hasActiveObservers(), is(false));
+            }
+        });
+        final Observer observer2 = mock(Observer.class);
+        mLiveData.observe(mOwner, observer1);
+        mLiveData.observe(mOwner, observer2);
+        mLiveData.setValue("bla");
+        verify(observer1).onChanged(anyString());
+        verify(observer2, Mockito.never()).onChanged(anyString());
+        assertThat(mLiveData.hasObservers(), is(true));
+        assertThat(mLiveData.hasActiveObservers(), is(false));
+    }
+
+    @Test
+    public void testActiveChangeInCallback2() {
+        Observer<String> observer1 = spy(new Observer<String>() {
+            @Override
+            public void onChanged(@Nullable String s) {
+                assertThat(mInObserver, is(false));
+                mInObserver = true;
+                mRegistry.handleLifecycleEvent(ON_START);
+                assertThat(mLiveData.hasActiveObservers(), is(true));
+                mInObserver = false;
+            }
+        });
+        final Observer observer2 = spy(new FailReentranceObserver());
+        mLiveData.observeForever(observer1);
+        mLiveData.observe(mOwner, observer2);
+        mLiveData.setValue("bla");
+        verify(observer1).onChanged(anyString());
+        verify(observer2).onChanged(anyString());
+        assertThat(mLiveData.hasObservers(), is(true));
+        assertThat(mLiveData.hasActiveObservers(), is(true));
+    }
+
+    @Test
+    public void testObserverRemovalInCallback() {
+        mRegistry.handleLifecycleEvent(ON_START);
+        Observer<String> observer = spy(new Observer<String>() {
+            @Override
+            public void onChanged(@Nullable String s) {
+                assertThat(mLiveData.hasObservers(), is(true));
+                mLiveData.removeObserver(this);
+                assertThat(mLiveData.hasObservers(), is(false));
+            }
+        });
+        mLiveData.observe(mOwner, observer);
+        mLiveData.setValue("bla");
+        verify(observer).onChanged(anyString());
+        assertThat(mLiveData.hasObservers(), is(false));
+    }
+
+    @Test
+    public void testObserverAdditionInCallback() {
+        mRegistry.handleLifecycleEvent(ON_START);
+        final Observer observer2 = spy(new FailReentranceObserver());
+        Observer<String> observer1 = spy(new Observer<String>() {
+            @Override
+            public void onChanged(@Nullable String s) {
+                assertThat(mInObserver, is(false));
+                mInObserver = true;
+                mLiveData.observe(mOwner, observer2);
+                assertThat(mLiveData.hasObservers(), is(true));
+                assertThat(mLiveData.hasActiveObservers(), is(true));
+                mInObserver = false;
+            }
+        });
+        mLiveData.observe(mOwner, observer1);
+        mLiveData.setValue("bla");
+        verify(observer1).onChanged(anyString());
+        verify(observer2).onChanged(anyString());
+        assertThat(mLiveData.hasObservers(), is(true));
+        assertThat(mLiveData.hasActiveObservers(), is(true));
+    }
+
+    @Test
+    public void testObserverWithoutLifecycleOwner() {
+        Observer<String> observer = (Observer<String>) mock(Observer.class);
+        mLiveData.setValue("boring");
+        mLiveData.observeForever(observer);
+        verify(mActiveObserversChanged).onCall(true);
+        verify(observer).onChanged("boring");
+        mLiveData.setValue("tihs");
+        verify(observer).onChanged("tihs");
+        mLiveData.removeObserver(observer);
+        verify(mActiveObserversChanged).onCall(false);
+        mLiveData.setValue("boring");
+        reset(observer);
+        verify(observer, never()).onChanged(anyString());
+    }
+
+    @Test
+    public void testSetValueDuringSetValue() {
+        mRegistry.handleLifecycleEvent(ON_START);
+        final Observer observer1 = spy(new Observer<String>() {
+            @Override
+            public void onChanged(String o) {
+                assertThat(mInObserver, is(false));
+                mInObserver = true;
+                if (o.equals(("bla"))) {
+                    mLiveData.setValue("gt");
+                }
+                mInObserver = false;
+            }
+        });
+        final Observer observer2 = spy(new FailReentranceObserver());
+        mLiveData.observe(mOwner, observer1);
+        mLiveData.observe(mOwner, observer2);
+        mLiveData.setValue("bla");
+        verify(observer1, Mockito.atMost(2)).onChanged("gt");
+        verify(observer2, Mockito.atMost(2)).onChanged("gt");
+    }
+
+    @Test
+    public void testDataChangeDuringStateChange() {
+        mRegistry.handleLifecycleEvent(ON_START);
+        mRegistry.addObserver(new LifecycleObserver() {
+            @OnLifecycleEvent(ON_STOP)
+            public void onStop() {
+                // change data in onStop, observer should not be called!
+                mLiveData.setValue("b");
+            }
+        });
+        Observer<String> observer = (Observer<String>) mock(Observer.class);
+        mLiveData.setValue("a");
+        mLiveData.observe(mOwner, observer);
+        verify(observer).onChanged("a");
+        mRegistry.handleLifecycleEvent(ON_PAUSE);
+        mRegistry.handleLifecycleEvent(ON_STOP);
+        verify(observer, never()).onChanged("b");
+
+        mRegistry.handleLifecycleEvent(ON_RESUME);
+        verify(observer).onChanged("b");
+    }
+
+    @Test
+    public void testNotCallInactiveWithObserveForever() {
+        mRegistry.handleLifecycleEvent(ON_START);
+        Observer<String> observer = (Observer<String>) mock(Observer.class);
+        Observer<String> observer2 = (Observer<String>) mock(Observer.class);
+        mLiveData.observe(mOwner, observer);
+        mLiveData.observeForever(observer2);
+        verify(mActiveObserversChanged).onCall(true);
+        reset(mActiveObserversChanged);
+        mRegistry.handleLifecycleEvent(ON_STOP);
+        verify(mActiveObserversChanged, never()).onCall(anyBoolean());
+        mRegistry.handleLifecycleEvent(ON_START);
+        verify(mActiveObserversChanged, never()).onCall(anyBoolean());
+    }
+
+    @SuppressWarnings("WeakerAccess")
+    static class PublicLiveData<T> extends LiveData<T> {
+        // cannot spy due to internal calls
+        public MethodExec activeObserversChanged;
+
+        @Override
+        protected void onActive() {
+            if (activeObserversChanged != null) {
+                activeObserversChanged.onCall(true);
+            }
+        }
+
+        @Override
+        protected void onInactive() {
+            if (activeObserversChanged != null) {
+                activeObserversChanged.onCall(false);
+            }
+        }
+    }
+
+    private class FailReentranceObserver<T> implements Observer<T> {
+        @Override
+        public void onChanged(@Nullable T t) {
+            assertThat(mInObserver, is(false));
+        }
+    }
+
+    interface MethodExec {
+        void onCall(boolean value);
+    }
+}
diff --git a/lifecycle/extensions/src/test/java/android/arch/lifecycle/MediatorLiveDataTest.java b/lifecycle/extensions/src/test/java/android/arch/lifecycle/MediatorLiveDataTest.java
new file mode 100644
index 0000000..f522230
--- /dev/null
+++ b/lifecycle/extensions/src/test/java/android/arch/lifecycle/MediatorLiveDataTest.java
@@ -0,0 +1,233 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.arch.lifecycle;
+
+import static org.hamcrest.CoreMatchers.is;
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.mockito.Matchers.any;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.reset;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import android.arch.core.executor.AppToolkitTaskExecutor;
+import android.arch.lifecycle.util.InstantTaskExecutor;
+import android.support.annotation.Nullable;
+import android.support.test.filters.SmallTest;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+
+@SuppressWarnings("unchecked")
+@SmallTest
+@RunWith(JUnit4.class)
+public class MediatorLiveDataTest {
+
+    private LifecycleOwner mOwner;
+    private LifecycleRegistry mRegistry;
+    private MediatorLiveData<String> mMediator;
+    private LiveData<String> mSource;
+    private boolean mSourceActive;
+
+    @Before
+    public void setup() {
+        mOwner = mock(LifecycleOwner.class);
+        mRegistry = new LifecycleRegistry(mOwner);
+        when(mOwner.getLifecycle()).thenReturn(mRegistry);
+        mMediator = new MediatorLiveData<>();
+        mSource = new LiveData<String>() {
+            @Override
+            protected void onActive() {
+                mSourceActive = true;
+            }
+
+            @Override
+            protected void onInactive() {
+                mSourceActive = false;
+            }
+        };
+        mSourceActive = false;
+        mMediator.observe(mOwner, mock(Observer.class));
+        mRegistry.handleLifecycleEvent(Lifecycle.Event.ON_CREATE);
+        mRegistry.handleLifecycleEvent(Lifecycle.Event.ON_START);
+    }
+
+    @Before
+    public void swapExecutorDelegate() {
+        AppToolkitTaskExecutor.getInstance().setDelegate(new InstantTaskExecutor());
+    }
+
+    @Test
+    public void testSingleDelivery() {
+        Observer observer = mock(Observer.class);
+        mMediator.addSource(mSource, observer);
+        mSource.setValue("flatfoot");
+        verify(observer).onChanged("flatfoot");
+        mRegistry.handleLifecycleEvent(Lifecycle.Event.ON_STOP);
+        reset(observer);
+        verify(observer, never()).onChanged(any());
+    }
+
+    @Test
+    public void testChangeWhileInactive() {
+        Observer observer = mock(Observer.class);
+        mMediator.addSource(mSource, observer);
+        mMediator.observe(mOwner, mock(Observer.class));
+        mSource.setValue("one");
+        verify(observer).onChanged("one");
+        mRegistry.handleLifecycleEvent(Lifecycle.Event.ON_STOP);
+        reset(observer);
+        mSource.setValue("flatfoot");
+        mRegistry.handleLifecycleEvent(Lifecycle.Event.ON_START);
+        verify(observer).onChanged("flatfoot");
+    }
+
+
+    @Test
+    public void testAddSourceToActive() {
+        mSource.setValue("flatfoot");
+        Observer observer = mock(Observer.class);
+        mMediator.addSource(mSource, observer);
+        verify(observer).onChanged("flatfoot");
+    }
+
+    @Test
+    public void testAddSourceToInActive() {
+        mSource.setValue("flatfoot");
+        mRegistry.handleLifecycleEvent(Lifecycle.Event.ON_STOP);
+        Observer observer = mock(Observer.class);
+        mMediator.addSource(mSource, observer);
+        verify(observer, never()).onChanged(any());
+        mRegistry.handleLifecycleEvent(Lifecycle.Event.ON_START);
+        verify(observer).onChanged("flatfoot");
+    }
+
+    @Test
+    public void testRemoveSource() {
+        mSource.setValue("flatfoot");
+        Observer observer = mock(Observer.class);
+        mMediator.addSource(mSource, observer);
+        verify(observer).onChanged("flatfoot");
+        mMediator.removeSource(mSource);
+        reset(observer);
+        mSource.setValue("failure");
+        verify(observer, never()).onChanged(any());
+    }
+
+    @Test
+    public void testSourceInactive() {
+        Observer observer = mock(Observer.class);
+        mMediator.addSource(mSource, observer);
+        assertThat(mSourceActive, is(true));
+        mRegistry.handleLifecycleEvent(Lifecycle.Event.ON_STOP);
+        assertThat(mSourceActive, is(false));
+        mRegistry.handleLifecycleEvent(Lifecycle.Event.ON_START);
+        assertThat(mSourceActive, is(true));
+    }
+
+    @Test
+    public void testNoLeakObserver() {
+        // Imitates a destruction of a ViewModel: a listener of LiveData is destroyed,
+        // a reference to MediatorLiveData is cleaned up. In this case we shouldn't leak
+        // MediatorLiveData as an observer of mSource.
+        assertThat(mSource.hasObservers(), is(false));
+        Observer observer = mock(Observer.class);
+        mMediator.addSource(mSource, observer);
+        assertThat(mSource.hasObservers(), is(true));
+        mRegistry.handleLifecycleEvent(Lifecycle.Event.ON_STOP);
+        mRegistry.handleLifecycleEvent(Lifecycle.Event.ON_DESTROY);
+        mMediator = null;
+        assertThat(mSource.hasObservers(), is(false));
+    }
+
+    @Test
+    public void testMultipleSources() {
+        Observer observer1 = mock(Observer.class);
+        mMediator.addSource(mSource, observer1);
+        MutableLiveData<Integer> source2 = new MutableLiveData<>();
+        Observer observer2 = mock(Observer.class);
+        mMediator.addSource(source2, observer2);
+        mSource.setValue("flatfoot");
+        verify(observer1).onChanged("flatfoot");
+        verify(observer2, never()).onChanged(any());
+        reset(observer1, observer2);
+        source2.setValue(1703);
+        verify(observer1, never()).onChanged(any());
+        verify(observer2).onChanged(1703);
+        reset(observer1, observer2);
+        mRegistry.handleLifecycleEvent(Lifecycle.Event.ON_STOP);
+        mSource.setValue("failure");
+        source2.setValue(0);
+        verify(observer1, never()).onChanged(any());
+        verify(observer2, never()).onChanged(any());
+    }
+
+    @Test
+    public void removeSourceDuringOnActive() {
+        // to trigger ConcurrentModificationException,
+        // we have to call remove from a collection during "for" loop.
+        // ConcurrentModificationException is thrown from next() method of an iterator
+        // so this modification shouldn't be at the last iteration,
+        // because if it is a last iteration, then next() wouldn't be called.
+        // And the last: an order of an iteration over sources is not defined,
+        // so I have to call it remove operation  from all observers.
+        mRegistry.handleLifecycleEvent(Lifecycle.Event.ON_STOP);
+        Observer<String> removingObserver = new Observer<String>() {
+            @Override
+            public void onChanged(@Nullable String s) {
+                mMediator.removeSource(mSource);
+            }
+        };
+        mMediator.addSource(mSource, removingObserver);
+        MutableLiveData<String> source2 = new MutableLiveData<>();
+        source2.setValue("nana");
+        mMediator.addSource(source2, removingObserver);
+        mSource.setValue("petjack");
+        mRegistry.handleLifecycleEvent(Lifecycle.Event.ON_START);
+    }
+
+    @Test(expected = IllegalArgumentException.class)
+    public void reAddSameSourceWithDifferentObserver() {
+        mMediator.addSource(mSource, mock(Observer.class));
+        mMediator.addSource(mSource, mock(Observer.class));
+    }
+
+    @Test
+    public void addSourceDuringOnActive() {
+        mRegistry.handleLifecycleEvent(Lifecycle.Event.ON_STOP);
+        mSource.setValue("a");
+        mMediator.addSource(mSource, new Observer<String>() {
+            @Override
+            public void onChanged(@Nullable String s) {
+                MutableLiveData<String> source = new MutableLiveData<>();
+                source.setValue("b");
+                mMediator.addSource(source, new Observer<String>() {
+                    @Override
+                    public void onChanged(@Nullable String s) {
+                        mMediator.setValue("c");
+                    }
+                });
+            }
+        });
+        mRegistry.handleLifecycleEvent(Lifecycle.Event.ON_START);
+        assertThat(mMediator.getValue(), is("c"));
+    }
+
+}
diff --git a/lifecycle/extensions/src/test/java/android/arch/lifecycle/ThreadedLiveDataTest.java b/lifecycle/extensions/src/test/java/android/arch/lifecycle/ThreadedLiveDataTest.java
new file mode 100644
index 0000000..e578546
--- /dev/null
+++ b/lifecycle/extensions/src/test/java/android/arch/lifecycle/ThreadedLiveDataTest.java
@@ -0,0 +1,96 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.arch.lifecycle;
+
+import static android.arch.lifecycle.Lifecycle.Event.ON_START;
+
+import static org.hamcrest.CoreMatchers.is;
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
+
+import android.arch.core.executor.JunitTaskExecutorRule;
+import android.arch.core.executor.TaskExecutor;
+import android.support.annotation.Nullable;
+import android.support.test.filters.SmallTest;
+
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.TimeUnit;
+
+@SmallTest
+public class ThreadedLiveDataTest {
+
+    private static final int TIMEOUT_SECS = 3;
+
+    @Rule
+    public JunitTaskExecutorRule mTaskExecutorRule = new JunitTaskExecutorRule(1, false);
+
+    private LiveData<String> mLiveData;
+    private LifecycleOwner mLifecycleOwner;
+    private LifecycleRegistry mRegistry;
+
+    @Before
+    public void init() {
+        mLiveData = new MutableLiveData<>();
+        mLifecycleOwner = mock(LifecycleOwner.class);
+        mRegistry = new LifecycleRegistry(mLifecycleOwner);
+        when(mLifecycleOwner.getLifecycle()).thenReturn(mRegistry);
+    }
+
+    @Test
+    public void testPostValue() throws InterruptedException {
+        final TaskExecutor taskExecutor = mTaskExecutorRule.getTaskExecutor();
+        final CountDownLatch finishTestLatch = new CountDownLatch(1);
+        final Observer<String> observer = new Observer<String>() {
+            @Override
+            public void onChanged(@Nullable String newValue) {
+                try {
+                    assertThat(taskExecutor.isMainThread(), is(true));
+                    assertThat(newValue, is("success"));
+                } finally {
+                    finishTestLatch.countDown();
+                }
+            }
+        };
+        taskExecutor.executeOnMainThread(new Runnable() {
+            @Override
+            public void run() {
+                mRegistry.handleLifecycleEvent(ON_START);
+                mLiveData.observe(mLifecycleOwner, observer);
+                final CountDownLatch latch = new CountDownLatch(1);
+                taskExecutor.executeOnDiskIO(new Runnable() {
+                    @Override
+                    public void run() {
+                        mLiveData.postValue("fail");
+                        mLiveData.postValue("success");
+                        latch.countDown();
+                    }
+                });
+                try {
+                    assertThat(latch.await(TIMEOUT_SECS, TimeUnit.SECONDS), is(true));
+                } catch (InterruptedException e) {
+                    throw new RuntimeException(e);
+                }
+            }
+        });
+        assertThat(finishTestLatch.await(TIMEOUT_SECS, TimeUnit.SECONDS), is(true));
+    }
+}
diff --git a/lifecycle/extensions/src/test/java/android/arch/lifecycle/TransformationsTest.java b/lifecycle/extensions/src/test/java/android/arch/lifecycle/TransformationsTest.java
new file mode 100644
index 0000000..8c808ed
--- /dev/null
+++ b/lifecycle/extensions/src/test/java/android/arch/lifecycle/TransformationsTest.java
@@ -0,0 +1,196 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.arch.lifecycle;
+
+import static org.hamcrest.CoreMatchers.is;
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.mockito.Matchers.anyString;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.reset;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import android.arch.core.executor.AppToolkitTaskExecutor;
+import android.arch.core.util.Function;
+import android.arch.lifecycle.util.InstantTaskExecutor;
+import android.support.test.filters.SmallTest;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+
+@SuppressWarnings("unchecked")
+@SmallTest
+@RunWith(JUnit4.class)
+public class TransformationsTest {
+
+    private LifecycleOwner mOwner;
+
+    @Before
+    public void swapExecutorDelegate() {
+        AppToolkitTaskExecutor.getInstance().setDelegate(new InstantTaskExecutor());
+    }
+
+    @Before
+    public void setup() {
+        mOwner = mock(LifecycleOwner.class);
+        LifecycleRegistry registry = new LifecycleRegistry(mOwner);
+        when(mOwner.getLifecycle()).thenReturn(registry);
+        registry.handleLifecycleEvent(Lifecycle.Event.ON_CREATE);
+        registry.handleLifecycleEvent(Lifecycle.Event.ON_START);
+    }
+
+    @Test
+    public void testMap() {
+        LiveData<String> source = new MutableLiveData<>();
+        LiveData<Integer> mapped = Transformations.map(source, new Function<String, Integer>() {
+            @Override
+            public Integer apply(String input) {
+                return input.length();
+            }
+        });
+        Observer<Integer> observer = mock(Observer.class);
+        mapped.observe(mOwner, observer);
+        source.setValue("four");
+        verify(observer).onChanged(4);
+    }
+
+    @Test
+    public void testSwitchMap() {
+        LiveData<Integer> trigger = new MutableLiveData<>();
+        final LiveData<String> first = new MutableLiveData<>();
+        final LiveData<String> second = new MutableLiveData<>();
+        LiveData<String> result = Transformations.switchMap(trigger,
+                new Function<Integer, LiveData<String>>() {
+                    @Override
+                    public LiveData<String> apply(Integer input) {
+                        if (input == 1) {
+                            return first;
+                        } else {
+                            return second;
+                        }
+                    }
+                });
+
+        Observer<String> observer = mock(Observer.class);
+        result.observe(mOwner, observer);
+        verify(observer, never()).onChanged(anyString());
+        first.setValue("first");
+        trigger.setValue(1);
+        verify(observer).onChanged("first");
+        second.setValue("second");
+        reset(observer);
+        verify(observer, never()).onChanged(anyString());
+        trigger.setValue(2);
+        verify(observer).onChanged("second");
+        reset(observer);
+        first.setValue("failure");
+        verify(observer, never()).onChanged(anyString());
+    }
+
+    @Test
+    public void testSwitchMap2() {
+        LiveData<Integer> trigger = new MutableLiveData<>();
+        final LiveData<String> first = new MutableLiveData<>();
+        final LiveData<String> second = new MutableLiveData<>();
+        LiveData<String> result = Transformations.switchMap(trigger,
+                new Function<Integer, LiveData<String>>() {
+                    @Override
+                    public LiveData<String> apply(Integer input) {
+                        if (input == 1) {
+                            return first;
+                        } else {
+                            return second;
+                        }
+                    }
+                });
+
+        Observer<String> observer = mock(Observer.class);
+        result.observe(mOwner, observer);
+
+        verify(observer, never()).onChanged(anyString());
+        trigger.setValue(1);
+        verify(observer, never()).onChanged(anyString());
+        first.setValue("fi");
+        verify(observer).onChanged("fi");
+        first.setValue("rst");
+        verify(observer).onChanged("rst");
+
+        second.setValue("second");
+        reset(observer);
+        verify(observer, never()).onChanged(anyString());
+        trigger.setValue(2);
+        verify(observer).onChanged("second");
+        reset(observer);
+        first.setValue("failure");
+        verify(observer, never()).onChanged(anyString());
+    }
+
+    @Test
+    public void testNoRedispatchSwitchMap() {
+        LiveData<Integer> trigger = new MutableLiveData<>();
+        final LiveData<String> first = new MutableLiveData<>();
+        LiveData<String> result = Transformations.switchMap(trigger,
+                new Function<Integer, LiveData<String>>() {
+                    @Override
+                    public LiveData<String> apply(Integer input) {
+                        return first;
+                    }
+                });
+
+        Observer<String> observer = mock(Observer.class);
+        result.observe(mOwner, observer);
+        verify(observer, never()).onChanged(anyString());
+        first.setValue("first");
+        trigger.setValue(1);
+        verify(observer).onChanged("first");
+        reset(observer);
+        trigger.setValue(2);
+        verify(observer, never()).onChanged(anyString());
+    }
+
+    @Test
+    public void testSwitchMapToNull() {
+        LiveData<Integer> trigger = new MutableLiveData<>();
+        final LiveData<String> first = new MutableLiveData<>();
+        LiveData<String> result = Transformations.switchMap(trigger,
+                new Function<Integer, LiveData<String>>() {
+                    @Override
+                    public LiveData<String> apply(Integer input) {
+                        if (input == 1) {
+                            return first;
+                        } else {
+                            return null;
+                        }
+                    }
+                });
+
+        Observer<String> observer = mock(Observer.class);
+        result.observe(mOwner, observer);
+        verify(observer, never()).onChanged(anyString());
+        first.setValue("first");
+        trigger.setValue(1);
+        verify(observer).onChanged("first");
+        reset(observer);
+
+        trigger.setValue(2);
+        verify(observer, never()).onChanged(anyString());
+        assertThat(first.hasObservers(), is(false));
+    }
+}
diff --git a/lifecycle/extensions/src/test/java/android/arch/lifecycle/ViewModelProviderTest.java b/lifecycle/extensions/src/test/java/android/arch/lifecycle/ViewModelProviderTest.java
new file mode 100644
index 0000000..61760fc
--- /dev/null
+++ b/lifecycle/extensions/src/test/java/android/arch/lifecycle/ViewModelProviderTest.java
@@ -0,0 +1,96 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.arch.lifecycle;
+
+import static org.hamcrest.CoreMatchers.is;
+import static org.hamcrest.CoreMatchers.notNullValue;
+import static org.hamcrest.MatcherAssert.assertThat;
+
+import android.arch.lifecycle.ViewModelProvider.NewInstanceFactory;
+
+import org.junit.Assert;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+
+@RunWith(JUnit4.class)
+public class ViewModelProviderTest {
+
+    private ViewModelProvider mViewModelProvider;
+
+    @Before
+    public void setup() {
+        mViewModelProvider = new ViewModelProvider(new ViewModelStore(), new NewInstanceFactory());
+    }
+
+    @Test
+    public void twoViewModelsWithSameKey() throws Throwable {
+        String key = "the_key";
+        ViewModel1 vm1 = mViewModelProvider.get(key, ViewModel1.class);
+        assertThat(vm1.mCleared, is(false));
+        ViewModel2 vw2 = mViewModelProvider.get(key, ViewModel2.class);
+        assertThat(vw2, notNullValue());
+        assertThat(vm1.mCleared, is(true));
+    }
+
+
+    @Test
+    public void localViewModel() throws Throwable {
+        class VM extends ViewModel1 {
+        }
+        try {
+            mViewModelProvider.get(VM.class);
+            Assert.fail();
+        } catch (IllegalArgumentException ignored) {
+        }
+    }
+
+    @Test
+    public void twoViewModels() {
+        ViewModel1 model1 = mViewModelProvider.get(ViewModel1.class);
+        ViewModel2 model2 = mViewModelProvider.get(ViewModel2.class);
+        assertThat(mViewModelProvider.get(ViewModel1.class), is(model1));
+        assertThat(mViewModelProvider.get(ViewModel2.class), is(model2));
+    }
+
+    @Test
+    public void testOwnedBy() {
+        final ViewModelStore store = new ViewModelStore();
+        ViewModelStoreOwner owner = new ViewModelStoreOwner() {
+            @Override
+            public ViewModelStore getViewModelStore() {
+                return store;
+            }
+        };
+        ViewModelProvider provider = new ViewModelProvider(owner, new NewInstanceFactory());
+        ViewModel1 viewModel = provider.get(ViewModel1.class);
+        assertThat(viewModel, is(provider.get(ViewModel1.class)));
+    }
+
+    public static class ViewModel1 extends ViewModel {
+        boolean mCleared;
+
+        @Override
+        protected void onCleared() {
+            mCleared = true;
+        }
+    }
+
+    public static class ViewModel2 extends ViewModel {
+    }
+}
diff --git a/lifecycle/extensions/src/test/java/android/arch/lifecycle/ViewModelStoreTest.java b/lifecycle/extensions/src/test/java/android/arch/lifecycle/ViewModelStoreTest.java
new file mode 100644
index 0000000..cfeefb7
--- /dev/null
+++ b/lifecycle/extensions/src/test/java/android/arch/lifecycle/ViewModelStoreTest.java
@@ -0,0 +1,54 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.arch.lifecycle;
+
+import static org.hamcrest.CoreMatchers.is;
+import static org.hamcrest.CoreMatchers.nullValue;
+import static org.hamcrest.MatcherAssert.assertThat;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+
+@RunWith(JUnit4.class)
+public class ViewModelStoreTest {
+
+    @Test
+    public void testClear() {
+        ViewModelStore store = new ViewModelStore();
+        TestViewModel viewModel1 = new TestViewModel();
+        TestViewModel viewModel2 = new TestViewModel();
+        store.put("a", viewModel1);
+        store.put("b", viewModel2);
+        assertThat(viewModel1.mCleared, is(false));
+        assertThat(viewModel2.mCleared, is(false));
+        store.clear();
+        assertThat(viewModel1.mCleared, is(true));
+        assertThat(viewModel2.mCleared, is(true));
+        assertThat(store.get("a"), nullValue());
+        assertThat(store.get("b"), nullValue());
+    }
+
+    static class TestViewModel extends ViewModel {
+        boolean mCleared = false;
+
+        @Override
+        protected void onCleared() {
+            mCleared = true;
+        }
+    }
+}
diff --git a/lifecycle/extensions/src/test/java/android/arch/lifecycle/util/InstantTaskExecutor.java b/lifecycle/extensions/src/test/java/android/arch/lifecycle/util/InstantTaskExecutor.java
new file mode 100644
index 0000000..52318fd
--- /dev/null
+++ b/lifecycle/extensions/src/test/java/android/arch/lifecycle/util/InstantTaskExecutor.java
@@ -0,0 +1,36 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.arch.lifecycle.util;
+
+import android.arch.core.executor.TaskExecutor;
+
+public class InstantTaskExecutor extends TaskExecutor {
+    @Override
+    public void executeOnDiskIO(Runnable runnable) {
+        runnable.run();
+    }
+
+    @Override
+    public void postToMainThread(Runnable runnable) {
+        runnable.run();
+    }
+
+    @Override
+    public boolean isMainThread() {
+        return true;
+    }
+}
diff --git a/lifecycle/gradle.properties b/lifecycle/gradle.properties
new file mode 100644
index 0000000..88d2a75
--- /dev/null
+++ b/lifecycle/gradle.properties
@@ -0,0 +1,17 @@
+#
+# Copyright (C) 2016 The Android Open Source Project
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#      http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+
+org.gradle.jvmargs=-Xmx3000M
\ No newline at end of file
diff --git a/lifecycle/gradle/wrapper/gradle-wrapper.jar b/lifecycle/gradle/wrapper/gradle-wrapper.jar
new file mode 100644
index 0000000..13372ae
--- /dev/null
+++ b/lifecycle/gradle/wrapper/gradle-wrapper.jar
Binary files differ
diff --git a/lifecycle/gradle/wrapper/gradle-wrapper.properties b/lifecycle/gradle/wrapper/gradle-wrapper.properties
new file mode 100644
index 0000000..467c103
--- /dev/null
+++ b/lifecycle/gradle/wrapper/gradle-wrapper.properties
@@ -0,0 +1,6 @@
+#Mon Dec 28 10:00:20 PST 2015
+distributionBase=GRADLE_USER_HOME
+distributionPath=wrapper/dists
+zipStoreBase=GRADLE_USER_HOME
+zipStorePath=wrapper/dists
+distributionUrl=https\://services.gradle.org/distributions/gradle-3.2-all.zip
diff --git a/lifecycle/gradlew b/lifecycle/gradlew
new file mode 100755
index 0000000..9d82f78
--- /dev/null
+++ b/lifecycle/gradlew
@@ -0,0 +1,160 @@
+#!/usr/bin/env bash
+
+##############################################################################
+##
+##  Gradle start up script for UN*X
+##
+##############################################################################
+
+# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
+DEFAULT_JVM_OPTS=""
+
+APP_NAME="Gradle"
+APP_BASE_NAME=`basename "$0"`
+
+# Use the maximum available, or set MAX_FD != -1 to use that value.
+MAX_FD="maximum"
+
+warn ( ) {
+    echo "$*"
+}
+
+die ( ) {
+    echo
+    echo "$*"
+    echo
+    exit 1
+}
+
+# OS specific support (must be 'true' or 'false').
+cygwin=false
+msys=false
+darwin=false
+case "`uname`" in
+  CYGWIN* )
+    cygwin=true
+    ;;
+  Darwin* )
+    darwin=true
+    ;;
+  MINGW* )
+    msys=true
+    ;;
+esac
+
+# Attempt to set APP_HOME
+# Resolve links: $0 may be a link
+PRG="$0"
+# Need this for relative symlinks.
+while [ -h "$PRG" ] ; do
+    ls=`ls -ld "$PRG"`
+    link=`expr "$ls" : '.*-> \(.*\)$'`
+    if expr "$link" : '/.*' > /dev/null; then
+        PRG="$link"
+    else
+        PRG=`dirname "$PRG"`"/$link"
+    fi
+done
+SAVED="`pwd`"
+cd "`dirname \"$PRG\"`/" >/dev/null
+APP_HOME="`pwd -P`"
+cd "$SAVED" >/dev/null
+
+CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
+
+# Determine the Java command to use to start the JVM.
+if [ -n "$JAVA_HOME" ] ; then
+    if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
+        # IBM's JDK on AIX uses strange locations for the executables
+        JAVACMD="$JAVA_HOME/jre/sh/java"
+    else
+        JAVACMD="$JAVA_HOME/bin/java"
+    fi
+    if [ ! -x "$JAVACMD" ] ; then
+        die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
+
+Please set the JAVA_HOME variable in your environment to match the
+location of your Java installation."
+    fi
+else
+    JAVACMD="java"
+    which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
+
+Please set the JAVA_HOME variable in your environment to match the
+location of your Java installation."
+fi
+
+# Increase the maximum file descriptors if we can.
+if [ "$cygwin" = "false" -a "$darwin" = "false" ] ; then
+    MAX_FD_LIMIT=`ulimit -H -n`
+    if [ $? -eq 0 ] ; then
+        if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then
+            MAX_FD="$MAX_FD_LIMIT"
+        fi
+        ulimit -n $MAX_FD
+        if [ $? -ne 0 ] ; then
+            warn "Could not set maximum file descriptor limit: $MAX_FD"
+        fi
+    else
+        warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT"
+    fi
+fi
+
+# For Darwin, add options to specify how the application appears in the dock
+if $darwin; then
+    GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\""
+fi
+
+# For Cygwin, switch paths to Windows format before running java
+if $cygwin ; then
+    APP_HOME=`cygpath --path --mixed "$APP_HOME"`
+    CLASSPATH=`cygpath --path --mixed "$CLASSPATH"`
+    JAVACMD=`cygpath --unix "$JAVACMD"`
+
+    # We build the pattern for arguments to be converted via cygpath
+    ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null`
+    SEP=""
+    for dir in $ROOTDIRSRAW ; do
+        ROOTDIRS="$ROOTDIRS$SEP$dir"
+        SEP="|"
+    done
+    OURCYGPATTERN="(^($ROOTDIRS))"
+    # Add a user-defined pattern to the cygpath arguments
+    if [ "$GRADLE_CYGPATTERN" != "" ] ; then
+        OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)"
+    fi
+    # Now convert the arguments - kludge to limit ourselves to /bin/sh
+    i=0
+    for arg in "$@" ; do
+        CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -`
+        CHECK2=`echo "$arg"|egrep -c "^-"`                                 ### Determine if an option
+
+        if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then                    ### Added a condition
+            eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"`
+        else
+            eval `echo args$i`="\"$arg\""
+        fi
+        i=$((i+1))
+    done
+    case $i in
+        (0) set -- ;;
+        (1) set -- "$args0" ;;
+        (2) set -- "$args0" "$args1" ;;
+        (3) set -- "$args0" "$args1" "$args2" ;;
+        (4) set -- "$args0" "$args1" "$args2" "$args3" ;;
+        (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;;
+        (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;;
+        (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;;
+        (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;;
+        (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;;
+    esac
+fi
+
+# Split up the JVM_OPTS And GRADLE_OPTS values into an array, following the shell quoting and substitution rules
+function splitJvmOpts() {
+    JVM_OPTS=("$@")
+}
+eval splitJvmOpts $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS
+JVM_OPTS[${#JVM_OPTS[*]}]="-Dorg.gradle.appname=$APP_BASE_NAME"
+
+exec "$JAVACMD" "${JVM_OPTS[@]}" -classpath "$CLASSPATH" org.gradle.wrapper.GradleWrapperMain "$@"
diff --git a/lifecycle/gradlew.bat b/lifecycle/gradlew.bat
new file mode 100644
index 0000000..aec9973
--- /dev/null
+++ b/lifecycle/gradlew.bat
@@ -0,0 +1,90 @@
+@if "%DEBUG%" == "" @echo off

+@rem ##########################################################################

+@rem

+@rem  Gradle startup script for Windows

+@rem

+@rem ##########################################################################

+

+@rem Set local scope for the variables with windows NT shell

+if "%OS%"=="Windows_NT" setlocal

+

+@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.

+set DEFAULT_JVM_OPTS=

+

+set DIRNAME=%~dp0

+if "%DIRNAME%" == "" set DIRNAME=.

+set APP_BASE_NAME=%~n0

+set APP_HOME=%DIRNAME%

+

+@rem Find java.exe

+if defined JAVA_HOME goto findJavaFromJavaHome

+

+set JAVA_EXE=java.exe

+%JAVA_EXE% -version >NUL 2>&1

+if "%ERRORLEVEL%" == "0" goto init

+

+echo.

+echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.

+echo.

+echo Please set the JAVA_HOME variable in your environment to match the

+echo location of your Java installation.

+

+goto fail

+

+:findJavaFromJavaHome

+set JAVA_HOME=%JAVA_HOME:"=%

+set JAVA_EXE=%JAVA_HOME%/bin/java.exe

+

+if exist "%JAVA_EXE%" goto init

+

+echo.

+echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%

+echo.

+echo Please set the JAVA_HOME variable in your environment to match the

+echo location of your Java installation.

+

+goto fail

+

+:init

+@rem Get command-line arguments, handling Windowz variants

+

+if not "%OS%" == "Windows_NT" goto win9xME_args

+if "%@eval[2+2]" == "4" goto 4NT_args

+

+:win9xME_args

+@rem Slurp the command line arguments.

+set CMD_LINE_ARGS=

+set _SKIP=2

+

+:win9xME_args_slurp

+if "x%~1" == "x" goto execute

+

+set CMD_LINE_ARGS=%*

+goto execute

+

+:4NT_args

+@rem Get arguments from the 4NT Shell from JP Software

+set CMD_LINE_ARGS=%$

+

+:execute

+@rem Setup the command line

+

+set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar

+

+@rem Execute Gradle

+"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS%

+

+:end

+@rem End local scope for the variables with windows NT shell

+if "%ERRORLEVEL%"=="0" goto mainEnd

+

+:fail

+rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of

+rem the _cmd.exe /c_ return code!

+if  not "" == "%GRADLE_EXIT_CONSOLE%" exit 1

+exit /b 1

+

+:mainEnd

+if "%OS%"=="Windows_NT" endlocal

+

+:omega

diff --git a/lifecycle/integration-tests/test-app/src/androidTest/java/com/android/support/lifecycle/LiveDataTransactionTest.java b/lifecycle/integration-tests/test-app/src/androidTest/java/com/android/support/lifecycle/LiveDataTransactionTest.java
new file mode 100644
index 0000000..443662a
--- /dev/null
+++ b/lifecycle/integration-tests/test-app/src/androidTest/java/com/android/support/lifecycle/LiveDataTransactionTest.java
@@ -0,0 +1,77 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES 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.support.lifecycle;
+
+import static android.support.v4.app.FragmentManager.FragmentLifecycleCallbacks;
+
+import static com.android.support.lifecycle.testapp.LiveDataTestActivity.LIVE_DATA_VALUE;
+
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.hamcrest.Matchers.is;
+
+import android.os.Bundle;
+import android.support.test.filters.SmallTest;
+import android.support.test.rule.ActivityTestRule;
+import android.support.v4.app.Fragment;
+import android.support.v4.app.FragmentManager;
+
+import com.android.support.lifecycle.testapp.LiveDataTestActivity;
+
+import org.junit.Assert;
+import org.junit.Rule;
+import org.junit.Test;
+
+@SmallTest
+public class LiveDataTransactionTest {
+
+    @Rule
+    public ActivityTestRule<LiveDataTestActivity> mActivityTestRule =
+            new ActivityTestRule<>(LiveDataTestActivity.class);
+
+    private boolean mVisited;
+
+    @Test
+    public void transactionInOnStateChanged() throws Throwable {
+        LiveDataTestActivity activity = mActivityTestRule.getActivity();
+
+        activity.getSupportFragmentManager().registerFragmentLifecycleCallbacks(
+                new FragmentLifecycleCallbacks() {
+                    @Override
+                    public void onFragmentCreated(FragmentManager fm, Fragment f,
+                            Bundle savedInstanceState) {
+                    }
+                }, true);
+        mActivityTestRule.runOnUiThread(() -> {
+            assertThat(activity.fragmentsNumber,  /** 2^MAX_DEPTH - 1 */ is(31));
+            activity.viewModel.liveData.observe(activity,
+                    s -> Assert.fail("savedInstance state triggered an update"));
+        });
+        LiveDataTestActivity newActivity = TestUtils.recreateActivity(activity,
+                mActivityTestRule);
+        TestUtils.waitTillResumed(newActivity, mActivityTestRule);
+        mActivityTestRule.runOnUiThread(() -> {
+            newActivity.viewModel.liveData.observe(newActivity,
+                    s -> {
+                        assertThat(s, is(LIVE_DATA_VALUE));
+                        mVisited = true;
+                    });
+            assertThat(newActivity.fragmentsNumber, /** 2 * (2^MAX_DEPTH - 1) + 1 */is(63));
+            assertThat(mVisited, is(true));
+        });
+    }
+
+}
diff --git a/lifecycle/integration-tests/test-app/src/main/java/com/android/support/lifecycle/testapp/LiveDataTestActivity.java b/lifecycle/integration-tests/test-app/src/main/java/com/android/support/lifecycle/testapp/LiveDataTestActivity.java
new file mode 100644
index 0000000..5bbd368
--- /dev/null
+++ b/lifecycle/integration-tests/test-app/src/main/java/com/android/support/lifecycle/testapp/LiveDataTestActivity.java
@@ -0,0 +1,105 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES 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.support.lifecycle.testapp;
+
+import android.os.Bundle;
+import android.support.annotation.Nullable;
+
+import com.android.support.lifecycle.LifecycleActivity;
+import com.android.support.lifecycle.LifecycleFragment;
+import com.android.support.lifecycle.LifecycleProvider;
+import com.android.support.lifecycle.LiveData;
+import com.android.support.lifecycle.ViewModel;
+import com.android.support.lifecycle.ViewModelStore;
+
+/**
+ * activity for LiveDataTransactionTest
+ */
+public class LiveDataTestActivity extends LifecycleActivity {
+
+    public static final String LIVE_DATA_VALUE = "saveInstanceState";
+    private static final int MAX_DEPTH = 5;
+    private static final String VM_TAG = "test";
+
+    /** view model*/
+    public LiveDataViewModel viewModel;
+    /** counter of created  */
+    public int fragmentsNumber;
+
+    /** ViewModel class */
+    public static class LiveDataViewModel extends ViewModel {
+        public LiveData<String> liveData = new LiveData<>();
+    }
+
+    /** Counting Fragment */
+    public static class CountingFragment extends LifecycleFragment {
+        @Override
+        public void onCreate(@Nullable Bundle savedInstanceState) {
+            super.onCreate(savedInstanceState);
+            ((LiveDataTestActivity) getActivity()).fragmentsNumber++;
+        }
+    }
+
+    /** a fragment which injects new fragment on new value of livedata */
+    public static class InternalFragment extends CountingFragment {
+
+        int mDepth = MAX_DEPTH;
+
+        @Override
+        public void onCreate(@Nullable Bundle savedInstanceState) {
+            super.onCreate(savedInstanceState);
+            LiveDataViewModel liveDataViewModel = ViewModelStore.get(
+                    (LifecycleProvider) getActivity(), VM_TAG, LiveDataViewModel.class);
+            liveDataViewModel.liveData.observe(this, s ->
+                    getChildFragmentManager().beginTransaction().add(new CountingFragment(),
+                            s).commitNow());
+
+            if (mDepth == MAX_DEPTH) {
+                return;
+            }
+            InternalFragment aFragment = new InternalFragment();
+            aFragment.mDepth = mDepth + 1;
+            InternalFragment bFragment = new InternalFragment();
+            bFragment.mDepth = mDepth + 1;
+            getChildFragmentManager().beginTransaction()
+                    .add(aFragment, getTag() + "_" + mDepth + "_a")
+                    .add(bFragment, getTag() + "_" + mDepth + "_b")
+                    .commitNow();
+        }
+    }
+
+    @Override
+    protected void onCreate(@Nullable Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+        viewModel = ViewModelStore.get(this, VM_TAG, LiveDataViewModel.class);
+        viewModel.liveData.observe(this, s ->
+                getSupportFragmentManager().beginTransaction().add(new CountingFragment(),
+                        s).commit());
+        String tag = "0_a";
+        if (getSupportFragmentManager().findFragmentByTag(tag) == null) {
+            InternalFragment internalFragment = new InternalFragment();
+            internalFragment.mDepth = 1;
+            getSupportFragmentManager().beginTransaction().add(internalFragment, tag).commitNow();
+        }
+    }
+
+    @Override
+    protected void onSaveInstanceState(Bundle outState) {
+        super.onSaveInstanceState(outState);
+        viewModel.liveData.setValue(LIVE_DATA_VALUE);
+    }
+}
diff --git a/lifecycle/integration-tests/testapp/.gitignore b/lifecycle/integration-tests/testapp/.gitignore
new file mode 100644
index 0000000..796b96d
--- /dev/null
+++ b/lifecycle/integration-tests/testapp/.gitignore
@@ -0,0 +1 @@
+/build
diff --git a/lifecycle/integration-tests/testapp/build.gradle b/lifecycle/integration-tests/testapp/build.gradle
new file mode 100644
index 0000000..b0da333
--- /dev/null
+++ b/lifecycle/integration-tests/testapp/build.gradle
@@ -0,0 +1,74 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+apply plugin: 'com.android.application'
+
+project.ext.noDocs = true
+
+android {
+    compileSdkVersion tools.current_sdk
+    buildToolsVersion tools.build_tools_version
+
+    defaultConfig {
+        applicationId "android.arch.lifecycle.testapp"
+        minSdkVersion flatfoot.min_sdk
+        targetSdkVersion tools.current_sdk
+        versionCode 1
+        versionName "1.0"
+
+        testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
+    }
+
+    testOptions {
+        unitTests.returnDefaultValues = true
+    }
+
+    compileOptions {
+        sourceCompatibility JavaVersion.VERSION_1_8
+        targetCompatibility JavaVersion.VERSION_1_8
+    }
+    buildTypes {
+        // test coverage does not work w/ jack
+        debug {
+            testCoverageEnabled = false
+        }
+        release {
+            testCoverageEnabled = false
+        }
+    }
+}
+
+dependencies {
+    compile fileTree(dir: 'libs', include: ['*.jar'])
+    // IJ canont figure out transitive dependencies so need to declare them.
+    compile project(":lifecycle:common")
+    compile project(":lifecycle:runtime")
+    compile project(":lifecycle:extensions")
+    annotationProcessor project(":lifecycle:compiler")
+    androidTestAnnotationProcessor project(":lifecycle:compiler")
+    androidTestCompile(libs.test_runner) {
+        exclude module: 'support-annotations'
+    }
+    androidTestCompile(libs.espresso_core, {
+        exclude group: 'com.android.support', module: 'support-annotations'
+    })
+    testCompile libs.junit
+}
+createAndroidCheckstyle(project)
+
+tasks['check'].dependsOn(tasks['connectedCheck'])
+
+uploadArchives.enabled = false
diff --git a/lifecycle/integration-tests/testapp/proguard-rules.pro b/lifecycle/integration-tests/testapp/proguard-rules.pro
new file mode 100644
index 0000000..575c04e
--- /dev/null
+++ b/lifecycle/integration-tests/testapp/proguard-rules.pro
@@ -0,0 +1,25 @@
+# Add project specific ProGuard rules here.
+# By default, the flags in this file are appended to flags specified
+# in /Users/sergeyv/Library/Android/sdk/tools/proguard/proguard-android.txt
+# You can edit the include path and order by changing the proguardFiles
+# directive in build.gradle.
+#
+# For more details, see
+#   http://developer.android.com/guide/developing/tools/proguard.html
+
+# Add any project specific keep options here:
+
+# If your project uses WebView with JS, uncomment the following
+# and specify the fully qualified class name to the JavaScript interface
+# class:
+#-keepclassmembers class fqcn.of.javascript.interface.for.webview {
+#   public *;
+#}
+
+# Uncomment this to preserve the line number information for
+# debugging stack traces.
+#-keepattributes SourceFile,LineNumberTable
+
+# If you keep the line number information, uncomment this to
+# hide the original source file name.
+#-renamesourcefileattribute SourceFile
diff --git a/lifecycle/integration-tests/testapp/src/androidTest/java/android/arch/lifecycle/ActivityFullLifecycleTest.java b/lifecycle/integration-tests/testapp/src/androidTest/java/android/arch/lifecycle/ActivityFullLifecycleTest.java
new file mode 100644
index 0000000..ee4e661
--- /dev/null
+++ b/lifecycle/integration-tests/testapp/src/androidTest/java/android/arch/lifecycle/ActivityFullLifecycleTest.java
@@ -0,0 +1,95 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.arch.lifecycle;
+
+import static android.arch.lifecycle.Lifecycle.Event.ON_CREATE;
+import static android.arch.lifecycle.Lifecycle.Event.ON_DESTROY;
+import static android.arch.lifecycle.Lifecycle.Event.ON_PAUSE;
+import static android.arch.lifecycle.Lifecycle.Event.ON_RESUME;
+import static android.arch.lifecycle.Lifecycle.Event.ON_START;
+import static android.arch.lifecycle.Lifecycle.Event.ON_STOP;
+import static android.arch.lifecycle.testapp.TestEvent.ACTIVITY_CALLBACK;
+import static android.arch.lifecycle.testapp.TestEvent.LIFECYCLE_EVENT;
+
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.hamcrest.Matchers.is;
+
+import android.app.Activity;
+import android.arch.lifecycle.Lifecycle.Event;
+import android.arch.lifecycle.testapp.CollectingActivity;
+import android.arch.lifecycle.testapp.FrameworkLifecycleRegistryActivity;
+import android.arch.lifecycle.testapp.FullLifecycleTestActivity;
+import android.arch.lifecycle.testapp.SupportLifecycleRegistryActivity;
+import android.arch.lifecycle.testapp.TestEvent;
+import android.support.test.filters.SmallTest;
+import android.support.test.rule.ActivityTestRule;
+import android.util.Pair;
+
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+
+import java.util.ArrayList;
+import java.util.List;
+
+@SmallTest
+@RunWith(Parameterized.class)
+public class ActivityFullLifecycleTest {
+    @Rule
+    public ActivityTestRule activityTestRule =
+            new ActivityTestRule<>(FullLifecycleTestActivity.class);
+
+    @Parameterized.Parameters
+    public static Class[] params() {
+        return new Class[]{FullLifecycleTestActivity.class,
+                SupportLifecycleRegistryActivity.class,
+                FrameworkLifecycleRegistryActivity.class};
+    }
+
+    public ActivityFullLifecycleTest(Class<? extends Activity> activityClass) {
+        //noinspection unchecked
+        activityTestRule = new ActivityTestRule(activityClass);
+    }
+
+
+    @Test
+    public void testFullLifecycle() throws InterruptedException {
+        Activity activity = activityTestRule.getActivity();
+        List<Pair<TestEvent, Event>> results = ((CollectingActivity) activity)
+                .waitForCollectedEvents();
+
+        Event[] expectedEvents =
+                new Event[]{ON_CREATE, ON_START, ON_RESUME, ON_PAUSE, ON_STOP, ON_DESTROY};
+
+        List<Pair<TestEvent, Event>> expected = new ArrayList<>();
+        boolean beforeResume = true;
+        for (Event i : expectedEvents) {
+            if (beforeResume) {
+                expected.add(new Pair<>(ACTIVITY_CALLBACK, i));
+                expected.add(new Pair<>(LIFECYCLE_EVENT, i));
+            } else {
+                expected.add(new Pair<>(LIFECYCLE_EVENT, i));
+                expected.add(new Pair<>(ACTIVITY_CALLBACK, i));
+            }
+            if (i == ON_RESUME) {
+                beforeResume = false;
+            }
+        }
+        assertThat(results, is(expected));
+    }
+}
diff --git a/lifecycle/integration-tests/testapp/src/androidTest/java/android/arch/lifecycle/FragmentInBackStackLifecycleTest.java b/lifecycle/integration-tests/testapp/src/androidTest/java/android/arch/lifecycle/FragmentInBackStackLifecycleTest.java
new file mode 100644
index 0000000..3397f5f
--- /dev/null
+++ b/lifecycle/integration-tests/testapp/src/androidTest/java/android/arch/lifecycle/FragmentInBackStackLifecycleTest.java
@@ -0,0 +1,99 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.arch.lifecycle;
+
+
+import static android.arch.lifecycle.Lifecycle.Event.ON_CREATE;
+import static android.arch.lifecycle.Lifecycle.Event.ON_DESTROY;
+import static android.arch.lifecycle.Lifecycle.Event.ON_PAUSE;
+import static android.arch.lifecycle.Lifecycle.Event.ON_RESUME;
+import static android.arch.lifecycle.Lifecycle.Event.ON_START;
+import static android.arch.lifecycle.Lifecycle.Event.ON_STOP;
+import static android.arch.lifecycle.TestUtils.recreateActivity;
+
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.hamcrest.Matchers.is;
+import static org.hamcrest.Matchers.iterableWithSize;
+
+import static java.util.Arrays.asList;
+
+import android.arch.lifecycle.Lifecycle.Event;
+import android.arch.lifecycle.testapp.EmptyActivity;
+import android.arch.lifecycle.testapp.R;
+import android.support.test.filters.SmallTest;
+import android.support.test.rule.ActivityTestRule;
+import android.support.test.runner.AndroidJUnit4;
+import android.support.v4.app.FragmentActivity;
+import android.support.v4.app.FragmentManager;
+
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.util.ArrayList;
+
+@SmallTest
+@RunWith(AndroidJUnit4.class)
+public class FragmentInBackStackLifecycleTest {
+    @Rule
+    public ActivityTestRule<EmptyActivity> activityTestRule = new ActivityTestRule<>(
+            EmptyActivity.class);
+
+    @Test
+    public void test() throws Throwable {
+        final ArrayList<Event> collectedEvents = new ArrayList<>();
+        LifecycleObserver collectingObserver = new LifecycleObserver() {
+            @OnLifecycleEvent(Event.ON_ANY)
+            void onAny(LifecycleOwner owner, Event event) {
+                collectedEvents.add(event);
+            }
+        };
+        final FragmentActivity activity = activityTestRule.getActivity();
+        activityTestRule.runOnUiThread(() -> {
+            FragmentManager fm = activity.getSupportFragmentManager();
+            LifecycleFragment fragment = new LifecycleFragment();
+            fm.beginTransaction().add(R.id.fragment_container, fragment, "tag").addToBackStack(null)
+                    .commit();
+            fm.executePendingTransactions();
+
+            fragment.getLifecycle().addObserver(collectingObserver);
+            LifecycleFragment fragment2 = new LifecycleFragment();
+            fm.beginTransaction().replace(R.id.fragment_container, fragment2).addToBackStack(null)
+                    .commit();
+            fm.executePendingTransactions();
+            assertThat(collectedEvents, is(asList(ON_CREATE, ON_START, ON_RESUME,
+                    ON_PAUSE, ON_STOP)));
+            collectedEvents.clear();
+        });
+        EmptyActivity newActivity = recreateActivity(activityTestRule.getActivity(),
+                activityTestRule);
+
+        assertThat(collectedEvents, is(asList(ON_DESTROY)));
+        collectedEvents.clear();
+        EmptyActivity lastActivity = recreateActivity(newActivity, activityTestRule);
+        activityTestRule.runOnUiThread(() -> {
+            FragmentManager fm = lastActivity.getSupportFragmentManager();
+            LifecycleFragment fragment = (LifecycleFragment) fm.findFragmentByTag("tag");
+            fragment.getLifecycle().addObserver(collectingObserver);
+            assertThat(collectedEvents, iterableWithSize(0));
+            fm.popBackStackImmediate();
+            assertThat(collectedEvents, is(asList(ON_CREATE, ON_START, ON_RESUME)));
+        });
+    }
+
+
+}
diff --git a/lifecycle/integration-tests/testapp/src/androidTest/java/android/arch/lifecycle/ProcessOwnerTest.java b/lifecycle/integration-tests/testapp/src/androidTest/java/android/arch/lifecycle/ProcessOwnerTest.java
new file mode 100644
index 0000000..e80e11c
--- /dev/null
+++ b/lifecycle/integration-tests/testapp/src/androidTest/java/android/arch/lifecycle/ProcessOwnerTest.java
@@ -0,0 +1,165 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.arch.lifecycle;
+
+import static android.arch.lifecycle.Lifecycle.Event.ON_PAUSE;
+import static android.arch.lifecycle.Lifecycle.Event.ON_RESUME;
+import static android.arch.lifecycle.Lifecycle.Event.ON_START;
+import static android.arch.lifecycle.Lifecycle.Event.ON_STOP;
+import static android.arch.lifecycle.TestUtils.waitTillResumed;
+
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.hamcrest.Matchers.is;
+import static org.hamcrest.Matchers.notNullValue;
+
+import android.app.Instrumentation;
+import android.arch.lifecycle.Lifecycle.Event;
+import android.arch.lifecycle.testapp.NavigationDialogActivity;
+import android.arch.lifecycle.testapp.NavigationTestActivityFirst;
+import android.arch.lifecycle.testapp.NavigationTestActivitySecond;
+import android.content.Context;
+import android.content.Intent;
+import android.support.test.InstrumentationRegistry;
+import android.support.test.filters.SmallTest;
+import android.support.test.rule.ActivityTestRule;
+import android.support.test.runner.AndroidJUnit4;
+
+import org.junit.After;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+
+@SmallTest
+@RunWith(AndroidJUnit4.class)
+public class ProcessOwnerTest {
+
+    @Rule
+    public ActivityTestRule<NavigationTestActivityFirst> activityTestRule =
+            new ActivityTestRule<>(NavigationTestActivityFirst.class);
+
+    static class ProcessObserver implements LifecycleObserver {
+        volatile boolean mChangedState;
+
+        @OnLifecycleEvent(Event.ON_ANY)
+        void onEvent() {
+            mChangedState = true;
+        }
+    }
+
+    private ProcessObserver mObserver = new ProcessObserver();
+
+    @After
+    public void tearDown() {
+        try {
+            // reassure that our observer is removed.
+            removeProcessObserver(mObserver);
+        } catch (Throwable throwable) {
+            throwable.printStackTrace();
+        }
+    }
+
+    @Test
+    public void testNavigation() throws Throwable {
+        LifecycleActivity firstActivity = setupObserverOnResume();
+        Instrumentation.ActivityMonitor monitor = new Instrumentation.ActivityMonitor(
+                NavigationTestActivitySecond.class.getCanonicalName(), null, false);
+        Instrumentation instrumentation = InstrumentationRegistry.getInstrumentation();
+        instrumentation.addMonitor(monitor);
+
+        Intent intent = new Intent(firstActivity, NavigationTestActivitySecond.class);
+        firstActivity.finish();
+        firstActivity.startActivity(intent);
+
+        LifecycleActivity secondActivity = (LifecycleActivity) monitor.waitForActivity();
+        assertThat("Failed to navigate", secondActivity, notNullValue());
+        checkProcessObserverSilent(secondActivity);
+    }
+
+    @Test
+    public void testRecreation() throws Throwable {
+        LifecycleActivity activity = setupObserverOnResume();
+        LifecycleActivity recreated = TestUtils.recreateActivity(activity, activityTestRule);
+        assertThat("Failed to recreate", recreated, notNullValue());
+        checkProcessObserverSilent(recreated);
+    }
+
+    @Test
+    public void testPressHomeButton() throws Throwable {
+        setupObserverOnResume();
+
+        Instrumentation.ActivityMonitor monitor = new Instrumentation.ActivityMonitor(
+                NavigationDialogActivity.class.getCanonicalName(), null, false);
+        Instrumentation instrumentation = InstrumentationRegistry.getInstrumentation();
+        instrumentation.addMonitor(monitor);
+
+        NavigationTestActivityFirst activity = activityTestRule.getActivity();
+        activity.startActivity(new Intent(activity, NavigationDialogActivity.class));
+        LifecycleActivity dialogActivity = (LifecycleActivity) monitor.waitForActivity();
+        checkProcessObserverSilent(dialogActivity);
+
+        List<Event> events = Collections.synchronizedList(new ArrayList<>());
+
+        LifecycleObserver collectingObserver = new LifecycleObserver() {
+            @OnLifecycleEvent(Event.ON_ANY)
+            public void onStateChanged(LifecycleOwner provider, Event event) {
+                events.add(event);
+            }
+        };
+        addProcessObserver(collectingObserver);
+        events.clear();
+        assertThat(activity.moveTaskToBack(true), is(true));
+        Thread.sleep(ProcessLifecycleOwner.TIMEOUT_MS * 2);
+        assertThat(events.toArray(), is(new Event[]{ON_PAUSE, ON_STOP}));
+        events.clear();
+        Context context = InstrumentationRegistry.getContext();
+        context.startActivity(new Intent(activity, NavigationDialogActivity.class)
+                .addFlags(Intent.FLAG_ACTIVITY_NEW_TASK));
+        waitTillResumed(dialogActivity, activityTestRule);
+        assertThat(events.toArray(), is(new Event[]{ON_START, ON_RESUME}));
+        removeProcessObserver(collectingObserver);
+        dialogActivity.finish();
+    }
+
+    private LifecycleActivity setupObserverOnResume() throws Throwable {
+        LifecycleActivity firstActivity = activityTestRule.getActivity();
+        waitTillResumed(firstActivity, activityTestRule);
+        addProcessObserver(mObserver);
+        mObserver.mChangedState = false;
+        return firstActivity;
+    }
+
+    private void addProcessObserver(LifecycleObserver observer) throws Throwable {
+        activityTestRule.runOnUiThread(() ->
+                ProcessLifecycleOwner.get().getLifecycle().addObserver(observer));
+    }
+
+    private void removeProcessObserver(LifecycleObserver observer) throws Throwable {
+        activityTestRule.runOnUiThread(() ->
+                ProcessLifecycleOwner.get().getLifecycle().removeObserver(observer));
+    }
+
+    private void checkProcessObserverSilent(LifecycleActivity activity) throws Throwable {
+        waitTillResumed(activity, activityTestRule);
+        assertThat(mObserver.mChangedState, is(false));
+        activityTestRule.runOnUiThread(() ->
+                ProcessLifecycleOwner.get().getLifecycle().removeObserver(mObserver));
+    }
+}
diff --git a/lifecycle/integration-tests/testapp/src/androidTest/java/android/arch/lifecycle/SimpleAppFullLifecycleTest.java b/lifecycle/integration-tests/testapp/src/androidTest/java/android/arch/lifecycle/SimpleAppFullLifecycleTest.java
new file mode 100644
index 0000000..44d1e8a
--- /dev/null
+++ b/lifecycle/integration-tests/testapp/src/androidTest/java/android/arch/lifecycle/SimpleAppFullLifecycleTest.java
@@ -0,0 +1,105 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.arch.lifecycle;
+
+import static android.arch.lifecycle.Lifecycle.State.CREATED;
+
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.hamcrest.Matchers.is;
+import static org.hamcrest.Matchers.isIn;
+import static org.hamcrest.Matchers.notNullValue;
+
+import android.arch.lifecycle.Lifecycle.Event;
+import android.arch.lifecycle.Lifecycle.State;
+import android.arch.lifecycle.testapp.SimpleAppLifecycleTestActivity;
+import android.arch.lifecycle.testapp.SimpleAppLifecycleTestActivity.TestEventType;
+import android.support.test.filters.LargeTest;
+import android.support.test.rule.ActivityTestRule;
+import android.support.test.runner.AndroidJUnit4;
+import android.util.Pair;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.util.List;
+
+@LargeTest
+@RunWith(AndroidJUnit4.class)
+public class SimpleAppFullLifecycleTest {
+
+    @SuppressWarnings("unchecked")
+    private static final Pair[] EXPECTED_EVENTS_CONSTRUCTION =
+            new Pair[] {
+                new Pair(TestEventType.PROCESS_EVENT, Event.ON_CREATE),
+                new Pair(TestEventType.ACTIVITY_EVENT, Event.ON_CREATE),
+                new Pair(TestEventType.PROCESS_EVENT, Event.ON_START),
+                new Pair(TestEventType.ACTIVITY_EVENT, Event.ON_START),
+                new Pair(TestEventType.PROCESS_EVENT, Event.ON_RESUME),
+                new Pair(TestEventType.ACTIVITY_EVENT, Event.ON_RESUME),
+            };
+
+    @SuppressWarnings("unchecked")
+    private static final Pair[] EXPECTED_EVENTS_DESTRUCTION =
+            new Pair[]{
+
+                    new Pair(TestEventType.ACTIVITY_EVENT, Event.ON_PAUSE),
+                    new Pair(TestEventType.ACTIVITY_EVENT, Event.ON_STOP),
+                    new Pair(TestEventType.ACTIVITY_EVENT, Event.ON_DESTROY),
+
+                    new Pair(TestEventType.PROCESS_EVENT, Event.ON_PAUSE),
+                    new Pair(TestEventType.PROCESS_EVENT, Event.ON_STOP),
+            };
+    @Rule
+    public ActivityTestRule<SimpleAppLifecycleTestActivity> activityTestRule =
+            new ActivityTestRule<>(SimpleAppLifecycleTestActivity.class, false, false);
+
+    @Before
+    public void setup() {
+        // cool down period, so application state will become DESTROYED
+        try {
+            Thread.sleep(ProcessLifecycleOwner.TIMEOUT_MS * 2);
+        } catch (InterruptedException e) {
+            e.printStackTrace();
+        }
+        SimpleAppLifecycleTestActivity.startProcessObserver();
+    }
+
+    @After
+    public void tearDown() {
+        SimpleAppLifecycleTestActivity.stopProcessObserver();
+    }
+
+    @Test
+    public void testFullLifecycle() throws InterruptedException {
+        State currentState = ProcessLifecycleOwner.get().getLifecycle().getCurrentState();
+        assertThat(currentState, is(CREATED));
+        activityTestRule.launchActivity(null);
+        List<Pair<TestEventType, Event>> events = SimpleAppLifecycleTestActivity.awaitForEvents();
+        assertThat("Failed to await for events", events, notNullValue());
+        //noinspection ConstantConditions
+        assertThat(events.subList(0, 6).toArray(), is(EXPECTED_EVENTS_CONSTRUCTION));
+
+        // TODO: bug 35122523
+        for (Pair<TestEventType, Event> event: events.subList(6, 11)) {
+            assertThat(event, isIn(EXPECTED_EVENTS_DESTRUCTION));
+        }
+    }
+
+}
diff --git a/lifecycle/integration-tests/testapp/src/androidTest/java/android/arch/lifecycle/SynchronousActivityLifecycleTest.java b/lifecycle/integration-tests/testapp/src/androidTest/java/android/arch/lifecycle/SynchronousActivityLifecycleTest.java
new file mode 100644
index 0000000..141d612
--- /dev/null
+++ b/lifecycle/integration-tests/testapp/src/androidTest/java/android/arch/lifecycle/SynchronousActivityLifecycleTest.java
@@ -0,0 +1,197 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.arch.lifecycle;
+
+import static android.support.test.InstrumentationRegistry.getInstrumentation;
+
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.hamcrest.Matchers.is;
+
+import android.app.Activity;
+import android.app.Application;
+import android.app.Instrumentation;
+import android.arch.lifecycle.Lifecycle.Event;
+import android.arch.lifecycle.testapp.LifecycleTestActivity;
+import android.content.ComponentName;
+import android.content.Intent;
+import android.content.pm.ActivityInfo;
+import android.os.Build;
+import android.support.test.filters.SmallTest;
+import android.support.test.rule.UiThreadTestRule;
+import android.support.test.runner.AndroidJUnit4;
+
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.lang.reflect.Method;
+
+/**
+ * It tests that an event is dispatched immediately after a call of corresponding OnXXX method
+ * during an execution of performXXX
+ */
+@SmallTest
+@RunWith(AndroidJUnit4.class)
+public class SynchronousActivityLifecycleTest {
+
+    @Rule
+    public UiThreadTestRule uiThreadTestRule = new UiThreadTestRule();
+
+    @Test
+    public void testOnCreateCall() throws Throwable {
+        testSynchronousCall(Event.ON_CREATE,
+                activity -> {
+                },
+                activity -> getInstrumentation().callActivityOnCreate(activity, null));
+    }
+
+    @Test
+    public void testOnStartCall() throws Throwable {
+        testSynchronousCall(Lifecycle.Event.ON_START,
+                activity -> getInstrumentation().callActivityOnCreate(activity, null),
+                SynchronousActivityLifecycleTest::performStart);
+    }
+
+    @Test
+    public void testOnResumeCall() throws Throwable {
+        testSynchronousCall(Lifecycle.Event.ON_RESUME,
+                activity -> {
+                    getInstrumentation().callActivityOnCreate(activity, null);
+                    performStart(activity);
+                },
+                SynchronousActivityLifecycleTest::performResume);
+    }
+
+    @Test
+    public void testOnStopCall() throws Throwable {
+        testSynchronousCall(Lifecycle.Event.ON_STOP,
+                activity -> {
+                    getInstrumentation().callActivityOnCreate(activity, null);
+                    performStart(activity);
+                },
+                SynchronousActivityLifecycleTest::performStop);
+    }
+
+    @Test
+    public void testOnDestroyCall() throws Throwable {
+        testSynchronousCall(Lifecycle.Event.ON_DESTROY,
+                activity -> getInstrumentation().callActivityOnCreate(activity, null),
+                activity -> getInstrumentation().callActivityOnDestroy(activity));
+    }
+
+    public void testSynchronousCall(Event event, ActivityCall preInit, ActivityCall call)
+            throws Throwable {
+        uiThreadTestRule.runOnUiThread(() -> {
+            Intent intent = new Intent();
+            ComponentName cn = new ComponentName(LifecycleTestActivity.class.getPackage().getName(),
+                    LifecycleTestActivity.class.getName());
+            intent.setComponent(cn);
+            Instrumentation instrumentation = getInstrumentation();
+            try {
+                Application app =
+                        (Application) instrumentation.getTargetContext().getApplicationContext();
+                LifecycleTestActivity testActivity =
+                        (LifecycleTestActivity) instrumentation.newActivity(
+                                LifecycleTestActivity.class, instrumentation.getTargetContext(),
+                                null, app, intent, new ActivityInfo(), "bla", null, null, null);
+                preInit.call(testActivity);
+                TestObserver testObserver = new TestObserver(testActivity, event);
+                testActivity.getLifecycle().addObserver(testObserver);
+                testObserver.unmute();
+                call.call(testActivity);
+
+                assertThat(testObserver.mEventReceived, is(true));
+            } catch (Exception e) {
+                throw new Error(e);
+            }
+        });
+    }
+
+    // Instrumentation.callOnActivityCreate calls performCreate on mActivity,
+    // but Instrumentation.callOnActivityStart calls onStart instead of performStart. ¯\_(ツ)_/¯
+    private static void performStart(Activity activity) {
+        try {
+            Method m = Activity.class.getDeclaredMethod("performStart");
+            m.setAccessible(true);
+            m.invoke(activity);
+        } catch (Exception e) {
+            throw new Error(e);
+        }
+    }
+
+    private static void performResume(Activity activity) {
+        try {
+            Method m = Activity.class.getDeclaredMethod("performResume");
+            m.setAccessible(true);
+            m.invoke(activity);
+        } catch (Exception e) {
+            throw new Error(e);
+        }
+    }
+
+
+    private static void performStop(Activity activity) {
+        try {
+            if (Build.VERSION.SDK_INT >= 24) {
+                Method m = Activity.class.getDeclaredMethod("performStop", boolean.class);
+                m.setAccessible(true);
+                m.invoke(activity, false);
+            } else {
+                Method m = Activity.class.getDeclaredMethod("performStop");
+                m.setAccessible(true);
+                m.invoke(activity);
+            }
+        } catch (Exception e) {
+            throw new Error(e);
+        }
+    }
+
+    private static class TestObserver implements GenericLifecycleObserver {
+        private final LifecycleTestActivity mActivity;
+        private final Event mExpectedEvent;
+        boolean mEventReceived = false;
+        boolean mMuted = true;
+
+        private TestObserver(LifecycleTestActivity activity, Event expectedEvent) {
+            this.mActivity = activity;
+            this.mExpectedEvent = expectedEvent;
+        }
+
+        void unmute() {
+            mMuted = false;
+        }
+
+        @Override
+        public void onStateChanged(LifecycleOwner lifecycleOwner, Event event) {
+            if (mMuted) {
+                return;
+            }
+            assertThat(event, is(mExpectedEvent));
+            assertThat(mActivity.mLifecycleCallFinished, is(true));
+            mEventReceived = true;
+        }
+
+        @Override
+        public Object getReceiver() {
+            return null;
+        }
+    }
+
+    private interface ActivityCall {
+        void call(Activity activity);
+    }
+}
diff --git a/lifecycle/integration-tests/testapp/src/androidTest/java/android/arch/lifecycle/TestUtils.java b/lifecycle/integration-tests/testapp/src/androidTest/java/android/arch/lifecycle/TestUtils.java
new file mode 100644
index 0000000..c5a520f
--- /dev/null
+++ b/lifecycle/integration-tests/testapp/src/androidTest/java/android/arch/lifecycle/TestUtils.java
@@ -0,0 +1,82 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.arch.lifecycle;
+
+import static android.arch.lifecycle.Lifecycle.State.RESUMED;
+
+import android.app.Activity;
+import android.app.Instrumentation;
+import android.app.Instrumentation.ActivityMonitor;
+import android.support.test.InstrumentationRegistry;
+import android.support.test.rule.ActivityTestRule;
+
+import java.util.concurrent.CountDownLatch;
+
+public class TestUtils {
+
+    private static final long TIMEOUT_MS = 2000;
+
+    @SuppressWarnings("unchecked")
+    public static <T extends Activity> T recreateActivity(final T activity, ActivityTestRule rule)
+            throws Throwable {
+        ActivityMonitor monitor = new ActivityMonitor(
+                activity.getClass().getCanonicalName(), null, false);
+        Instrumentation instrumentation = InstrumentationRegistry.getInstrumentation();
+        instrumentation.addMonitor(monitor);
+        rule.runOnUiThread(activity::recreate);
+        T result;
+
+        // this guarantee that we will reinstall monitor between notifications about onDestroy
+        // and onCreate
+        //noinspection SynchronizationOnLocalVariableOrMethodParameter
+        synchronized (monitor) {
+            do {
+                // the documetation says "Block until an Activity is created
+                // that matches this monitor." This statement is true, but there are some other
+                // true statements like: "Block until an Activity is destoyed" or
+                // "Block until an Activity is resumed"...
+
+                // this call will release synchronization monitor's monitor
+                result = (T) monitor.waitForActivityWithTimeout(TIMEOUT_MS);
+                if (result == null) {
+                    throw new RuntimeException("Timeout. Failed to recreate an activity");
+                }
+            } while (result == activity);
+        }
+        return result;
+    }
+
+    static void waitTillResumed(final LifecycleActivity a, ActivityTestRule<?> activityRule)
+            throws Throwable {
+        final CountDownLatch latch = new CountDownLatch(1);
+        activityRule.runOnUiThread(() -> {
+            Lifecycle.State currentState = a.getLifecycle().getCurrentState();
+            if (currentState == RESUMED) {
+                latch.countDown();
+            }
+            a.getLifecycle().addObserver(new LifecycleObserver() {
+                @OnLifecycleEvent(Lifecycle.Event.ON_RESUME)
+                public void onStateChanged(LifecycleOwner provider) {
+                    latch.countDown();
+                    provider.getLifecycle().removeObserver(this);
+                }
+            });
+        });
+        latch.await();
+    }
+
+}
diff --git a/lifecycle/integration-tests/testapp/src/main/AndroidManifest.xml b/lifecycle/integration-tests/testapp/src/main/AndroidManifest.xml
new file mode 100644
index 0000000..bf88f97
--- /dev/null
+++ b/lifecycle/integration-tests/testapp/src/main/AndroidManifest.xml
@@ -0,0 +1,75 @@
+<?xml version="1.0" encoding="utf-8"?><!--
+  ~ Copyright (C) 2016 The Android Open Source Project
+  ~ Licensed under the Apache License, Version 2.0 (the "License");
+  ~ you may not use this file except in compliance with the License.
+  ~ You may obtain a copy of the License at
+  ~      http://www.apache.org/licenses/LICENSE-2.0
+  ~ Unless required by applicable law or agreed to in writing, software
+  ~ distributed under the License is distributed on an "AS IS" BASIS,
+  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  ~ See the License for the specific language governing permissions and
+  ~ limitations under the License.
+  -->
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:tools="http://schemas.android.com/tools" package="android.arch.lifecycle.testapp">
+
+    <application android:allowBackup="true" android:label="Test App" android:supportsRtl="true"
+        tools:ignore="AllowBackup,GoogleAppIndexingWarning,MissingApplicationIcon">
+        <activity android:name="android.arch.lifecycle.testapp.MainActivity">
+            <intent-filter>
+                <action android:name="android.intent.action.MAIN" />
+                <category android:name="android.intent.category.LAUNCHER" />
+            </intent-filter>
+        </activity>
+
+        <activity android:name="android.arch.lifecycle.testapp.LifecycleTestActivity">
+            <intent-filter>
+                <action android:name="android.intent.action.MAIN" />
+                <category android:name="android.intent.category.LAUNCHER" />
+            </intent-filter>
+        </activity>
+        <activity android:name="android.arch.lifecycle.testapp.FullLifecycleTestActivity">
+            <intent-filter>
+                <action android:name="android.intent.action.MAIN" />
+                <category android:name="android.intent.category.LAUNCHER" />
+            </intent-filter>
+        </activity>
+        <activity android:name="android.arch.lifecycle.testapp.SupportLifecycleRegistryActivity">
+            <intent-filter>
+                <action android:name="android.intent.action.MAIN" />
+                <category android:name="android.intent.category.LAUNCHER" />
+            </intent-filter>
+        </activity>
+        <activity android:name="android.arch.lifecycle.testapp.FrameworkLifecycleRegistryActivity">
+            <intent-filter>
+                <action android:name="android.intent.action.MAIN" />
+                <category android:name="android.intent.category.LAUNCHER" />
+            </intent-filter>
+        </activity>
+        <activity
+            android:name="android.arch.lifecycle.testapp.SimpleAppLifecycleTestActivity">
+            <intent-filter>
+                <action android:name="android.intent.action.MAIN" />
+                <category android:name="android.intent.category.LAUNCHER" />
+            </intent-filter>
+        </activity>
+        <activity android:name="android.arch.lifecycle.testapp.NavigationTestActivityFirst"
+            android:launchMode="singleTask">
+            <intent-filter>
+                <action android:name="android.intent.action.MAIN" />
+                <category android:name="android.intent.category.LAUNCHER" />
+            </intent-filter>
+        </activity>
+        <activity
+            android:name="android.arch.lifecycle.testapp.EmptyActivity">
+            <intent-filter>
+                <action android:name="android.intent.action.MAIN" />
+                <category android:name="android.intent.category.LAUNCHER" />
+            </intent-filter>
+        </activity>
+        <activity android:name=".NavigationTestActivitySecond" />
+        <activity android:name=".NavigationDialogActivity"
+            android:launchMode="singleTask"
+            android:theme="@android:style/Theme.DeviceDefault.Dialog" />
+    </application>
+</manifest>
diff --git a/lifecycle/integration-tests/testapp/src/main/java/android/arch/lifecycle/testapp/CollectingActivity.java b/lifecycle/integration-tests/testapp/src/main/java/android/arch/lifecycle/testapp/CollectingActivity.java
new file mode 100644
index 0000000..6e243b6
--- /dev/null
+++ b/lifecycle/integration-tests/testapp/src/main/java/android/arch/lifecycle/testapp/CollectingActivity.java
@@ -0,0 +1,37 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.arch.lifecycle.testapp;
+
+import android.arch.lifecycle.Lifecycle;
+import android.util.Pair;
+
+import java.util.List;
+
+/**
+ * For activities that collect their events.
+ */
+public interface CollectingActivity {
+    long TIMEOUT = 5;
+
+    /**
+     * Return collected events
+     *
+     * @return The list of collected events.
+     * @throws InterruptedException
+     */
+    List<Pair<TestEvent, Lifecycle.Event>> waitForCollectedEvents() throws InterruptedException;
+}
diff --git a/lifecycle/integration-tests/testapp/src/main/java/android/arch/lifecycle/testapp/EmptyActivity.java b/lifecycle/integration-tests/testapp/src/main/java/android/arch/lifecycle/testapp/EmptyActivity.java
new file mode 100644
index 0000000..68a82e1
--- /dev/null
+++ b/lifecycle/integration-tests/testapp/src/main/java/android/arch/lifecycle/testapp/EmptyActivity.java
@@ -0,0 +1,33 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.arch.lifecycle.testapp;
+
+import android.os.Bundle;
+import android.support.annotation.Nullable;
+import android.support.v4.app.FragmentActivity;
+
+/**
+ * empty activity
+ */
+public class EmptyActivity extends FragmentActivity {
+
+    @Override
+    protected void onCreate(@Nullable Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+        setContentView(R.layout.empty_activity_layout);
+    }
+}
diff --git a/lifecycle/integration-tests/testapp/src/main/java/android/arch/lifecycle/testapp/FrameworkLifecycleRegistryActivity.java b/lifecycle/integration-tests/testapp/src/main/java/android/arch/lifecycle/testapp/FrameworkLifecycleRegistryActivity.java
new file mode 100644
index 0000000..d8f4fb3
--- /dev/null
+++ b/lifecycle/integration-tests/testapp/src/main/java/android/arch/lifecycle/testapp/FrameworkLifecycleRegistryActivity.java
@@ -0,0 +1,97 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.arch.lifecycle.testapp;
+
+import static android.arch.lifecycle.testapp.TestEvent.ACTIVITY_CALLBACK;
+
+import android.app.Activity;
+import android.arch.lifecycle.Lifecycle;
+import android.arch.lifecycle.LifecycleRegistry;
+import android.arch.lifecycle.LifecycleRegistryOwner;
+import android.os.Bundle;
+import android.util.Pair;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.TimeUnit;
+
+/**
+ * LifecycleRegistryOwner that extends framework activity.
+ */
+public class FrameworkLifecycleRegistryActivity extends Activity implements
+        LifecycleRegistryOwner, CollectingActivity {
+    private LifecycleRegistry mLifecycleRegistry = new LifecycleRegistry(this);
+
+    @Override
+    public LifecycleRegistry getLifecycle() {
+        return mLifecycleRegistry;
+    }
+
+    private List<Pair<TestEvent, Lifecycle.Event>> mCollectedEvents = new ArrayList<>();
+    private TestObserver mTestObserver = new TestObserver(mCollectedEvents);
+    private CountDownLatch mLatch = new CountDownLatch(1);
+
+    @Override
+    protected void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+        mCollectedEvents.add(new Pair<>(ACTIVITY_CALLBACK, Lifecycle.Event.ON_CREATE));
+        getLifecycle().addObserver(mTestObserver);
+    }
+
+    @Override
+    protected void onStart() {
+        super.onStart();
+        mCollectedEvents.add(new Pair<>(ACTIVITY_CALLBACK, Lifecycle.Event.ON_START));
+    }
+
+    @Override
+    protected void onResume() {
+        super.onResume();
+        mCollectedEvents.add(new Pair<>(ACTIVITY_CALLBACK, Lifecycle.Event.ON_RESUME));
+        finish();
+    }
+
+    @Override
+    protected void onDestroy() {
+        super.onDestroy();
+        mCollectedEvents.add(new Pair<>(ACTIVITY_CALLBACK, Lifecycle.Event.ON_DESTROY));
+        mLatch.countDown();
+    }
+
+    @Override
+    protected void onStop() {
+        super.onStop();
+        mCollectedEvents.add(new Pair<>(ACTIVITY_CALLBACK, Lifecycle.Event.ON_STOP));
+    }
+
+    @Override
+    protected void onPause() {
+        super.onPause();
+        mCollectedEvents.add(new Pair<>(ACTIVITY_CALLBACK, Lifecycle.Event.ON_PAUSE));
+    }
+
+    /**
+     * awaits for all events and returns them.
+     */
+    @Override
+    public List<Pair<TestEvent, Lifecycle.Event>> waitForCollectedEvents()
+            throws InterruptedException {
+        mLatch.await(TIMEOUT, TimeUnit.SECONDS);
+        return mCollectedEvents;
+    }
+}
diff --git a/lifecycle/integration-tests/testapp/src/main/java/android/arch/lifecycle/testapp/FullLifecycleTestActivity.java b/lifecycle/integration-tests/testapp/src/main/java/android/arch/lifecycle/testapp/FullLifecycleTestActivity.java
new file mode 100644
index 0000000..5972b16
--- /dev/null
+++ b/lifecycle/integration-tests/testapp/src/main/java/android/arch/lifecycle/testapp/FullLifecycleTestActivity.java
@@ -0,0 +1,88 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.arch.lifecycle.testapp;
+
+import static android.arch.lifecycle.testapp.TestEvent.ACTIVITY_CALLBACK;
+
+import android.arch.lifecycle.Lifecycle;
+import android.arch.lifecycle.LifecycleActivity;
+import android.os.Bundle;
+import android.util.Pair;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.TimeUnit;
+
+/**
+ * Activity for testing full lifecycle
+ */
+public class FullLifecycleTestActivity extends LifecycleActivity implements CollectingActivity {
+
+    private List<Pair<TestEvent, Lifecycle.Event>> mCollectedEvents = new ArrayList<>();
+    private TestObserver mTestObserver = new TestObserver(mCollectedEvents);
+    private CountDownLatch mLatch = new CountDownLatch(1);
+
+    @Override
+    protected void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+        mCollectedEvents.add(new Pair<>(ACTIVITY_CALLBACK, Lifecycle.Event.ON_CREATE));
+        getLifecycle().addObserver(mTestObserver);
+    }
+
+    @Override
+    protected void onStart() {
+        super.onStart();
+        mCollectedEvents.add(new Pair<>(ACTIVITY_CALLBACK, Lifecycle.Event.ON_START));
+    }
+
+    @Override
+    protected void onResume() {
+        super.onResume();
+        mCollectedEvents.add(new Pair<>(ACTIVITY_CALLBACK, Lifecycle.Event.ON_RESUME));
+        finish();
+    }
+
+    @Override
+    protected void onDestroy() {
+        super.onDestroy();
+        mCollectedEvents.add(new Pair<>(ACTIVITY_CALLBACK, Lifecycle.Event.ON_DESTROY));
+        mLatch.countDown();
+    }
+
+    @Override
+    protected void onStop() {
+        super.onStop();
+        mCollectedEvents.add(new Pair<>(ACTIVITY_CALLBACK, Lifecycle.Event.ON_STOP));
+    }
+
+    @Override
+    protected void onPause() {
+        super.onPause();
+        mCollectedEvents.add(new Pair<>(ACTIVITY_CALLBACK, Lifecycle.Event.ON_PAUSE));
+    }
+
+    /**
+     * awaits for all events and returns them.
+     */
+    @Override
+    public List<Pair<TestEvent, Lifecycle.Event>> waitForCollectedEvents()
+            throws InterruptedException {
+        mLatch.await(TIMEOUT, TimeUnit.SECONDS);
+        return mCollectedEvents;
+    }
+}
diff --git a/lifecycle/integration-tests/testapp/src/main/java/android/arch/lifecycle/testapp/LifecycleTestActivity.java b/lifecycle/integration-tests/testapp/src/main/java/android/arch/lifecycle/testapp/LifecycleTestActivity.java
new file mode 100644
index 0000000..093ec7f
--- /dev/null
+++ b/lifecycle/integration-tests/testapp/src/main/java/android/arch/lifecycle/testapp/LifecycleTestActivity.java
@@ -0,0 +1,73 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.arch.lifecycle.testapp;
+
+import android.arch.lifecycle.LifecycleActivity;
+import android.os.Bundle;
+
+/**
+ * Activity for testing events by themselves
+ */
+public class LifecycleTestActivity extends LifecycleActivity {
+
+    /**
+     * identifies that
+     */
+    public boolean mLifecycleCallFinished;
+
+    @Override
+    protected void onCreate(Bundle savedInstanceState) {
+        mLifecycleCallFinished = false;
+        super.onCreate(savedInstanceState);
+        mLifecycleCallFinished = true;
+    }
+
+    @Override
+    protected void onStart() {
+        mLifecycleCallFinished = false;
+        super.onStart();
+        mLifecycleCallFinished = true;
+    }
+
+    @Override
+    protected void onResume() {
+        mLifecycleCallFinished = false;
+        super.onResume();
+        mLifecycleCallFinished = true;
+    }
+
+    @Override
+    protected void onPause() {
+        mLifecycleCallFinished = false;
+        super.onPause();
+        mLifecycleCallFinished = true;
+    }
+
+    @Override
+    protected void onStop() {
+        mLifecycleCallFinished = false;
+        super.onStop();
+        mLifecycleCallFinished = true;
+    }
+
+    @Override
+    protected void onDestroy() {
+        mLifecycleCallFinished = false;
+        super.onDestroy();
+        mLifecycleCallFinished = true;
+    }
+}
diff --git a/lifecycle/integration-tests/testapp/src/main/java/android/arch/lifecycle/testapp/MainActivity.java b/lifecycle/integration-tests/testapp/src/main/java/android/arch/lifecycle/testapp/MainActivity.java
new file mode 100644
index 0000000..b9d5914
--- /dev/null
+++ b/lifecycle/integration-tests/testapp/src/main/java/android/arch/lifecycle/testapp/MainActivity.java
@@ -0,0 +1,31 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.arch.lifecycle.testapp;
+
+import android.os.Bundle;
+import android.support.v4.app.FragmentActivity;
+
+/**
+ * Simple test activity
+ */
+public class MainActivity extends FragmentActivity {
+    @Override
+    protected void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+        setContentView(R.layout.activity);
+    }
+}
diff --git a/lifecycle/integration-tests/testapp/src/main/java/android/arch/lifecycle/testapp/NavigationDialogActivity.java b/lifecycle/integration-tests/testapp/src/main/java/android/arch/lifecycle/testapp/NavigationDialogActivity.java
new file mode 100644
index 0000000..709bd8d
--- /dev/null
+++ b/lifecycle/integration-tests/testapp/src/main/java/android/arch/lifecycle/testapp/NavigationDialogActivity.java
@@ -0,0 +1,25 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.arch.lifecycle.testapp;
+
+import android.arch.lifecycle.LifecycleActivity;
+
+/**
+ *  an activity with Dialog theme.
+ */
+public class NavigationDialogActivity extends LifecycleActivity {
+}
diff --git a/lifecycle/integration-tests/testapp/src/main/java/android/arch/lifecycle/testapp/NavigationTestActivityFirst.java b/lifecycle/integration-tests/testapp/src/main/java/android/arch/lifecycle/testapp/NavigationTestActivityFirst.java
new file mode 100644
index 0000000..f1847c9
--- /dev/null
+++ b/lifecycle/integration-tests/testapp/src/main/java/android/arch/lifecycle/testapp/NavigationTestActivityFirst.java
@@ -0,0 +1,25 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.arch.lifecycle.testapp;
+
+import android.arch.lifecycle.LifecycleActivity;
+
+/**
+ * Activity for ProcessOwnerTest
+ */
+public class NavigationTestActivityFirst extends LifecycleActivity {
+}
diff --git a/lifecycle/integration-tests/testapp/src/main/java/android/arch/lifecycle/testapp/NavigationTestActivitySecond.java b/lifecycle/integration-tests/testapp/src/main/java/android/arch/lifecycle/testapp/NavigationTestActivitySecond.java
new file mode 100644
index 0000000..221e927
--- /dev/null
+++ b/lifecycle/integration-tests/testapp/src/main/java/android/arch/lifecycle/testapp/NavigationTestActivitySecond.java
@@ -0,0 +1,25 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.arch.lifecycle.testapp;
+
+import android.arch.lifecycle.LifecycleActivity;
+
+/**
+ * Activity for ProcessOwnerTest
+ */
+public class NavigationTestActivitySecond extends LifecycleActivity {
+}
diff --git a/lifecycle/integration-tests/testapp/src/main/java/android/arch/lifecycle/testapp/SimpleAppLifecycleTestActivity.java b/lifecycle/integration-tests/testapp/src/main/java/android/arch/lifecycle/testapp/SimpleAppLifecycleTestActivity.java
new file mode 100644
index 0000000..6d61c5e
--- /dev/null
+++ b/lifecycle/integration-tests/testapp/src/main/java/android/arch/lifecycle/testapp/SimpleAppLifecycleTestActivity.java
@@ -0,0 +1,100 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.arch.lifecycle.testapp;
+
+import android.arch.lifecycle.Lifecycle;
+import android.arch.lifecycle.LifecycleActivity;
+import android.arch.lifecycle.LifecycleObserver;
+import android.arch.lifecycle.LifecycleOwner;
+import android.arch.lifecycle.OnLifecycleEvent;
+import android.arch.lifecycle.ProcessLifecycleOwner;
+import android.os.Bundle;
+import android.util.Pair;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.TimeUnit;
+
+/**
+ * Activity for SimpleAppFullLifecycleTest
+ */
+public class SimpleAppLifecycleTestActivity extends LifecycleActivity {
+
+    public enum TestEventType {
+        PROCESS_EVENT,
+        ACTIVITY_EVENT
+    }
+
+    private static final long TIMEOUT_SECS = 10; // secs
+
+    static class TestObserver implements LifecycleObserver {
+
+        private TestEventType mType;
+
+        TestObserver(TestEventType type) {
+            mType = type;
+        }
+
+        @SuppressWarnings("unused")
+        @OnLifecycleEvent(Lifecycle.Event.ON_ANY)
+        void onEvent(LifecycleOwner provider, Lifecycle.Event event) {
+            sCollectedEvents.add(new Pair<>(mType, event));
+            sLatch.countDown();
+        }
+    }
+
+    static List<Pair<TestEventType, Lifecycle.Event>> sCollectedEvents = new ArrayList<>();
+    static CountDownLatch sLatch = new CountDownLatch(11);
+
+    /**
+     * start process observer
+     */
+    public static void startProcessObserver() {
+        ProcessLifecycleOwner.get().getLifecycle().addObserver(sProcessObserver);
+    }
+
+    /**
+     * stop process observer
+     */
+    public static void stopProcessObserver() {
+        ProcessLifecycleOwner.get().getLifecycle().removeObserver(sProcessObserver);
+    }
+
+    private static TestObserver sProcessObserver = new TestObserver(TestEventType.PROCESS_EVENT);
+
+    @Override
+    protected void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+        getLifecycle().addObserver(new TestObserver(TestEventType.ACTIVITY_EVENT));
+    }
+
+    @Override
+    protected void onResume() {
+        super.onResume();
+        finish();
+    }
+
+    /**
+     * returns collected events
+     */
+    public static List<Pair<TestEventType, Lifecycle.Event>> awaitForEvents()
+            throws InterruptedException {
+        boolean success = sLatch.await(TIMEOUT_SECS, TimeUnit.SECONDS);
+        return success ? sCollectedEvents : null;
+    }
+}
diff --git a/lifecycle/integration-tests/testapp/src/main/java/android/arch/lifecycle/testapp/SupportLifecycleRegistryActivity.java b/lifecycle/integration-tests/testapp/src/main/java/android/arch/lifecycle/testapp/SupportLifecycleRegistryActivity.java
new file mode 100644
index 0000000..c46c6d3
--- /dev/null
+++ b/lifecycle/integration-tests/testapp/src/main/java/android/arch/lifecycle/testapp/SupportLifecycleRegistryActivity.java
@@ -0,0 +1,95 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.arch.lifecycle.testapp;
+
+import static android.arch.lifecycle.testapp.TestEvent.ACTIVITY_CALLBACK;
+
+import android.arch.lifecycle.Lifecycle.Event;
+import android.arch.lifecycle.LifecycleRegistry;
+import android.arch.lifecycle.LifecycleRegistryOwner;
+import android.os.Bundle;
+import android.support.v4.app.FragmentActivity;
+import android.util.Pair;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.TimeUnit;
+
+/**
+ * LifecycleRegistryOwner that extends FragmentActivity.
+ */
+public class SupportLifecycleRegistryActivity extends FragmentActivity implements
+        LifecycleRegistryOwner, CollectingActivity {
+    private LifecycleRegistry mLifecycleRegistry = new LifecycleRegistry(this);
+    @Override
+    public LifecycleRegistry getLifecycle() {
+        return mLifecycleRegistry;
+    }
+
+    private List<Pair<TestEvent, Event>> mCollectedEvents = new ArrayList<>();
+    private TestObserver mTestObserver = new TestObserver(mCollectedEvents);
+    private CountDownLatch mLatch = new CountDownLatch(1);
+
+    @Override
+    protected void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+        mCollectedEvents.add(new Pair<>(ACTIVITY_CALLBACK, Event.ON_CREATE));
+        getLifecycle().addObserver(mTestObserver);
+    }
+
+    @Override
+    protected void onStart() {
+        super.onStart();
+        mCollectedEvents.add(new Pair<>(ACTIVITY_CALLBACK, Event.ON_START));
+    }
+
+    @Override
+    protected void onResume() {
+        super.onResume();
+        mCollectedEvents.add(new Pair<>(ACTIVITY_CALLBACK, Event.ON_RESUME));
+        finish();
+    }
+
+    @Override
+    protected void onDestroy() {
+        super.onDestroy();
+        mCollectedEvents.add(new Pair<>(ACTIVITY_CALLBACK, Event.ON_DESTROY));
+        mLatch.countDown();
+    }
+
+    @Override
+    protected void onStop() {
+        super.onStop();
+        mCollectedEvents.add(new Pair<>(ACTIVITY_CALLBACK, Event.ON_STOP));
+    }
+
+    @Override
+    protected void onPause() {
+        super.onPause();
+        mCollectedEvents.add(new Pair<>(ACTIVITY_CALLBACK, Event.ON_PAUSE));
+    }
+
+    /**
+     * awaits for all events and returns them.
+     */
+    @Override
+    public List<Pair<TestEvent, Event>> waitForCollectedEvents() throws InterruptedException {
+        mLatch.await(TIMEOUT, TimeUnit.SECONDS);
+        return mCollectedEvents;
+    }
+}
diff --git a/lifecycle/integration-tests/testapp/src/main/java/android/arch/lifecycle/testapp/TestEvent.java b/lifecycle/integration-tests/testapp/src/main/java/android/arch/lifecycle/testapp/TestEvent.java
new file mode 100644
index 0000000..0929f84
--- /dev/null
+++ b/lifecycle/integration-tests/testapp/src/main/java/android/arch/lifecycle/testapp/TestEvent.java
@@ -0,0 +1,22 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.arch.lifecycle.testapp;
+
+public enum TestEvent {
+    ACTIVITY_CALLBACK,
+    LIFECYCLE_EVENT
+}
diff --git a/lifecycle/integration-tests/testapp/src/main/java/android/arch/lifecycle/testapp/TestObserver.java b/lifecycle/integration-tests/testapp/src/main/java/android/arch/lifecycle/testapp/TestObserver.java
new file mode 100644
index 0000000..c611239
--- /dev/null
+++ b/lifecycle/integration-tests/testapp/src/main/java/android/arch/lifecycle/testapp/TestObserver.java
@@ -0,0 +1,70 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.arch.lifecycle.testapp;
+
+import static android.arch.lifecycle.Lifecycle.Event.ON_CREATE;
+import static android.arch.lifecycle.Lifecycle.Event.ON_DESTROY;
+import static android.arch.lifecycle.Lifecycle.Event.ON_PAUSE;
+import static android.arch.lifecycle.Lifecycle.Event.ON_RESUME;
+import static android.arch.lifecycle.Lifecycle.Event.ON_START;
+import static android.arch.lifecycle.Lifecycle.Event.ON_STOP;
+import static android.arch.lifecycle.testapp.TestEvent.LIFECYCLE_EVENT;
+
+import android.arch.lifecycle.Lifecycle.Event;
+import android.arch.lifecycle.LifecycleObserver;
+import android.arch.lifecycle.LifecycleOwner;
+import android.arch.lifecycle.OnLifecycleEvent;
+import android.util.Pair;
+
+import java.util.List;
+
+class TestObserver implements LifecycleObserver {
+    private final List<Pair<TestEvent, Event>> mCollectedEvents;
+
+    TestObserver(List<Pair<TestEvent, Event>> collectedEvents) {
+        mCollectedEvents = collectedEvents;
+    }
+
+    @OnLifecycleEvent(ON_CREATE)
+    public void create(LifecycleOwner pr) {
+        mCollectedEvents.add(new Pair<>(LIFECYCLE_EVENT, ON_CREATE));
+    }
+
+    @OnLifecycleEvent(ON_START)
+    public void start(LifecycleOwner pr) {
+        mCollectedEvents.add(new Pair<>(LIFECYCLE_EVENT, ON_START));
+    }
+
+    @OnLifecycleEvent(ON_RESUME)
+    public void resume(LifecycleOwner pr) {
+        mCollectedEvents.add(new Pair<>(LIFECYCLE_EVENT, ON_RESUME));
+    }
+    @OnLifecycleEvent(ON_PAUSE)
+    public void pause(LifecycleOwner pr) {
+        mCollectedEvents.add(new Pair<>(LIFECYCLE_EVENT, ON_PAUSE));
+    }
+
+    @OnLifecycleEvent(ON_STOP)
+    public void stop(LifecycleOwner pr) {
+        mCollectedEvents.add(new Pair<>(LIFECYCLE_EVENT, ON_STOP));
+    }
+
+    @OnLifecycleEvent(ON_DESTROY)
+    public void destroy(LifecycleOwner pr) {
+        mCollectedEvents.add(new Pair<>(LIFECYCLE_EVENT, ON_DESTROY));
+    }
+}
diff --git a/lifecycle/integration-tests/testapp/src/main/java/android/arch/lifecycle/testapp/UsualFragment.java b/lifecycle/integration-tests/testapp/src/main/java/android/arch/lifecycle/testapp/UsualFragment.java
new file mode 100644
index 0000000..fb6cae0
--- /dev/null
+++ b/lifecycle/integration-tests/testapp/src/main/java/android/arch/lifecycle/testapp/UsualFragment.java
@@ -0,0 +1,37 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.arch.lifecycle.testapp;
+
+import android.os.Bundle;
+import android.support.annotation.Nullable;
+import android.support.v4.app.Fragment;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+
+/**
+ * Simple fragment which does nothing.
+ */
+public class UsualFragment extends Fragment {
+
+    @Nullable
+    @Override
+    public View onCreateView(LayoutInflater inflater, ViewGroup container,
+            Bundle savedInstanceState) {
+        return new View(getContext());
+    }
+}
diff --git a/lifecycle/integration-tests/testapp/src/main/res/layout/activity.xml b/lifecycle/integration-tests/testapp/src/main/res/layout/activity.xml
new file mode 100644
index 0000000..a4e9513
--- /dev/null
+++ b/lifecycle/integration-tests/testapp/src/main/res/layout/activity.xml
@@ -0,0 +1,32 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  ~ Copyright (C) 2015 The Android Open Source Project
+  ~ Licensed under the Apache License, Version 2.0 (the "License");
+  ~ you may not use this file except in compliance with the License.
+  ~ You may obtain a copy of the License at
+  ~      http://www.apache.org/licenses/LICENSE-2.0
+  ~ Unless required by applicable law or agreed to in writing, software
+  ~ distributed under the License is distributed on an "AS IS" BASIS,
+  ~ WITHOUT 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:layout_width="match_parent"
+    android:layout_height="match_parent">
+    <FrameLayout
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:id="@+id/root">
+        <fragment
+            android:id="@+id/main_fragment"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:tag="fragment_tag"
+            android:name="android.arch.lifecycle.testapp.UsualFragment"
+            tools:context="android.arch.lifecycle.testapp.MainActivity">
+        </fragment>
+    </FrameLayout>
+</FrameLayout>
diff --git a/lifecycle/integration-tests/testapp/src/main/res/layout/empty_activity_layout.xml b/lifecycle/integration-tests/testapp/src/main/res/layout/empty_activity_layout.xml
new file mode 100644
index 0000000..d476848
--- /dev/null
+++ b/lifecycle/integration-tests/testapp/src/main/res/layout/empty_activity_layout.xml
@@ -0,0 +1,29 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  ~ Copyright (C) 2017 The Android Open Source Project
+  ~
+  ~ Licensed under the Apache License, Version 2.0 (the "License");
+  ~ you may not use this file except in compliance with the License.
+  ~ You may obtain a copy of the License at
+  ~
+  ~      http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing, software
+  ~ distributed under the License is distributed on an "AS IS" BASIS,
+  ~ WITHOUT 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:id="@+id/activity_main"
+    android:layout_width="match_parent"
+    android:layout_height="match_parent"
+    tools:context="android.arch.lifecycle.activity.FragmentLifecycleActivity">
+    <FrameLayout
+        android:id="@+id/fragment_container"
+        android:layout_width="match_parent"
+        android:layout_height="match_parent" />
+</RelativeLayout>
diff --git a/lifecycle/reactivestreams/.gitignore b/lifecycle/reactivestreams/.gitignore
new file mode 100644
index 0000000..796b96d
--- /dev/null
+++ b/lifecycle/reactivestreams/.gitignore
@@ -0,0 +1 @@
+/build
diff --git a/lifecycle/reactivestreams/build.gradle b/lifecycle/reactivestreams/build.gradle
new file mode 100644
index 0000000..1d30331
--- /dev/null
+++ b/lifecycle/reactivestreams/build.gradle
@@ -0,0 +1,67 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+apply plugin: 'com.android.library'
+apply plugin: 'maven'
+
+sourceCompatibility = '1.7'
+
+android {
+    compileSdkVersion tools.current_sdk
+    buildToolsVersion tools.build_tools_version
+
+    defaultConfig {
+        minSdkVersion flatfoot.min_sdk
+        targetSdkVersion tools.current_sdk
+        versionCode 1
+        versionName "1.0"
+
+        testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
+    }
+    buildTypes {
+        release {
+            minifyEnabled false
+            proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
+        }
+    }
+
+    testOptions {
+        unitTests.returnDefaultValues = true
+    }
+}
+
+allprojects {
+    dependencies {
+        compile project(":arch:runtime")
+        compile project(":lifecycle:common")
+        compile project(":lifecycle:extensions")
+        compile project(":lifecycle:runtime")
+        compile libs.support.annotations
+        compile libs.reactive_streams
+
+        testCompile libs.junit
+        testCompile libs.rx_java
+
+        testCompile(libs.test_runner) {
+            exclude module: 'support-annotations'
+        }
+        androidTestCompile libs.support.app_compat
+    }
+}
+
+createAndroidCheckstyle(project)
+
+archivesBaseName = "reactivestreams"
diff --git a/lifecycle/reactivestreams/proguard-rules.pro b/lifecycle/reactivestreams/proguard-rules.pro
new file mode 100644
index 0000000..b7210d1
--- /dev/null
+++ b/lifecycle/reactivestreams/proguard-rules.pro
@@ -0,0 +1,17 @@
+# Add project specific ProGuard rules here.
+# By default, the flags in this file are appended to flags specified
+# in /Users/yboyar/android/sdk/tools/proguard/proguard-android.txt
+# You can edit the include path and order by changing the proguardFiles
+# directive in build.gradle.
+#
+# For more details, see
+#   http://developer.android.com/guide/developing/tools/proguard.html
+
+# Add any project specific keep options here:
+
+# If your project uses WebView with JS, uncomment the following
+# and specify the fully qualified class name to the JavaScript interface
+# class:
+#-keepclassmembers class fqcn.of.javascript.interface.for.webview {
+#   public *;
+#}
diff --git a/lifecycle/reactivestreams/src/androidTest/AndroidManifest.xml b/lifecycle/reactivestreams/src/androidTest/AndroidManifest.xml
new file mode 100644
index 0000000..722f480
--- /dev/null
+++ b/lifecycle/reactivestreams/src/androidTest/AndroidManifest.xml
@@ -0,0 +1,27 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  ~ Copyright (C) 2017 The Android Open Source Project
+  ~
+  ~ Licensed under the Apache License, Version 2.0 (the "License");
+  ~ you may not use this file except in compliance with the License.
+  ~ You may obtain a copy of the License at
+  ~
+  ~      http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing, software
+  ~ distributed under the License is distributed on an "AS IS" BASIS,
+  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  ~ See the License for the specific language governing permissions and
+  ~ limitations under the License.
+  -->
+
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+          package="android.arch.lifecycle.reactivestreams.test">
+
+    <application>
+        <activity android:name="android.arch.lifecycle.viewmodeltest.ViewModelActivity"
+                  android:theme="@style/Base.Theme.AppCompat">
+        </activity>
+    </application>
+
+</manifest>
diff --git a/lifecycle/reactivestreams/src/main/AndroidManifest.xml b/lifecycle/reactivestreams/src/main/AndroidManifest.xml
new file mode 100644
index 0000000..2210041
--- /dev/null
+++ b/lifecycle/reactivestreams/src/main/AndroidManifest.xml
@@ -0,0 +1,19 @@
+<!--
+  ~ Copyright (C) 2017 The Android Open Source Project
+  ~
+  ~ Licensed under the Apache License, Version 2.0 (the "License");
+  ~ you may not use this file except in compliance with the License.
+  ~ You may obtain a copy of the License at
+  ~
+  ~      http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing, software
+  ~ distributed under the License is distributed on an "AS IS" BASIS,
+  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  ~ See the License for the specific language governing permissions and
+  ~ limitations under the License.
+  -->
+
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+          package="android.arch.lifecycle.reactivestreams">
+</manifest>
diff --git a/lifecycle/reactivestreams/src/main/java/android/arch/lifecycle/LiveDataReactiveStreams.java b/lifecycle/reactivestreams/src/main/java/android/arch/lifecycle/LiveDataReactiveStreams.java
new file mode 100644
index 0000000..0be0149
--- /dev/null
+++ b/lifecycle/reactivestreams/src/main/java/android/arch/lifecycle/LiveDataReactiveStreams.java
@@ -0,0 +1,172 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.arch.lifecycle;
+
+import android.arch.core.executor.AppToolkitTaskExecutor;
+import android.support.annotation.Nullable;
+
+import org.reactivestreams.Publisher;
+import org.reactivestreams.Subscriber;
+import org.reactivestreams.Subscription;
+
+import java.lang.ref.WeakReference;
+
+/**
+ * Adapts {@link LiveData} input and output to the ReactiveStreams spec.
+ */
+@SuppressWarnings("WeakerAccess")
+public final class LiveDataReactiveStreams {
+    private LiveDataReactiveStreams() {
+    }
+
+    /**
+     * Adapts the given {@link LiveData} stream to a ReactiveStreams {@link Publisher}.
+     *
+     * <p>
+     * By using a good publisher implementation such as RxJava 2.x Flowables, most consumers will
+     * be able to let the library deal with backpressure using operators and not need to worry about
+     * ever manually calling {@link Subscription#request}.
+     *
+     * <p>
+     * On subscription to the publisher, the observer will attach to the given {@link LiveData}.
+     * Once {@link Subscription#request) is called on the subscription object, an observer will be
+     * connected to the data stream. Calling request(Long.MAX_VALUE) is equivalent to creating an
+     * unbounded stream with no backpressure. If request with a finite count reaches 0, the observer
+     * will buffer the latest item and emit it to the subscriber when data is again requested. Any
+     * other items emitted during the time there was no backpressure requested will be dropped.
+     */
+    public static <T> Publisher<T> toPublisher(
+            final LifecycleOwner lifecycle, final LiveData<T> liveData) {
+
+        return new Publisher<T>() {
+            boolean mObserving;
+            boolean mCanceled;
+            long mRequested;
+            @Nullable
+            T mLatest;
+
+            @Override
+            public void subscribe(final Subscriber<? super T> subscriber) {
+                final Observer<T> observer = new Observer<T>() {
+                    @Override
+                    public void onChanged(@Nullable T t) {
+                        if (mCanceled) {
+                            return;
+                        }
+                        if (mRequested > 0) {
+                            mLatest = null;
+                            subscriber.onNext(t);
+                            if (mRequested != Long.MAX_VALUE) {
+                                mRequested--;
+                            }
+                        } else {
+                            mLatest = t;
+                        }
+                    }
+                };
+
+                subscriber.onSubscribe(new Subscription() {
+                    @Override
+                    public void request(final long n) {
+                        if (n < 0 || mCanceled) {
+                            return;
+                        }
+                        AppToolkitTaskExecutor.getInstance().executeOnMainThread(new Runnable() {
+                            @Override
+                            public void run() {
+                                if (mCanceled) {
+                                    return;
+                                }
+                                // Prevent overflowage.
+                                mRequested = mRequested + n >= mRequested
+                                        ? mRequested + n : Long.MAX_VALUE;
+                                if (!mObserving) {
+                                    mObserving = true;
+                                    liveData.observe(lifecycle, observer);
+                                } else if (mLatest != null) {
+                                    observer.onChanged(mLatest);
+                                    mLatest = null;
+                                }
+                            }
+                        });
+                    }
+
+                    @Override
+                    public void cancel() {
+                        if (mCanceled) {
+                            return;
+                        }
+                        AppToolkitTaskExecutor.getInstance().executeOnMainThread(new Runnable() {
+                            @Override
+                            public void run() {
+                                if (mCanceled) {
+                                    return;
+                                }
+                                if (mObserving) {
+                                    liveData.removeObserver(observer);
+                                    mObserving = false;
+                                }
+                                mLatest = null;
+                                mCanceled = true;
+                            }
+                        });
+                    }
+                });
+            }
+
+        };
+    }
+
+    /**
+     * Creates an Observable {@link LiveData} stream from a ReactiveStreams publisher.
+     */
+    public static <T> LiveData<T> fromPublisher(final Publisher<T> publisher) {
+        MutableLiveData<T> liveData = new MutableLiveData<>();
+        // Since we don't have a way to directly observe cancels, weakly hold the live data.
+        final WeakReference<MutableLiveData<T>> liveDataRef = new WeakReference<>(liveData);
+
+        publisher.subscribe(new Subscriber<T>() {
+            @Override
+            public void onSubscribe(Subscription s) {
+                // Don't worry about backpressure. If the stream is too noisy then backpressure can
+                // be handled upstream.
+                s.request(Long.MAX_VALUE);
+            }
+
+            @Override
+            public void onNext(final T t) {
+                final LiveData<T> liveData = liveDataRef.get();
+                if (liveData != null) {
+                    liveData.postValue(t);
+                }
+            }
+
+            @Override
+            public void onError(Throwable t) {
+                // Errors should be handled upstream, so propagate as a crash.
+                throw new RuntimeException(t);
+            }
+
+            @Override
+            public void onComplete() {
+            }
+        });
+
+        return liveData;
+    }
+
+}
diff --git a/lifecycle/reactivestreams/src/test/java/android/arch/lifecycle/LiveDataReactiveStreamsTest.java b/lifecycle/reactivestreams/src/test/java/android/arch/lifecycle/LiveDataReactiveStreamsTest.java
new file mode 100644
index 0000000..87fba27
--- /dev/null
+++ b/lifecycle/reactivestreams/src/test/java/android/arch/lifecycle/LiveDataReactiveStreamsTest.java
@@ -0,0 +1,297 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.arch.lifecycle;
+
+import static android.arch.lifecycle.Lifecycle.State.RESUMED;
+
+import static org.hamcrest.CoreMatchers.is;
+import static org.hamcrest.MatcherAssert.assertThat;
+
+import android.arch.core.executor.AppToolkitTaskExecutor;
+import android.arch.core.executor.TaskExecutor;
+import android.support.annotation.Nullable;
+import android.support.test.filters.SmallTest;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.reactivestreams.Subscriber;
+import org.reactivestreams.Subscription;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.List;
+
+import io.reactivex.Flowable;
+import io.reactivex.disposables.Disposable;
+import io.reactivex.functions.Consumer;
+import io.reactivex.processors.PublishProcessor;
+import io.reactivex.processors.ReplayProcessor;
+import io.reactivex.schedulers.TestScheduler;
+import io.reactivex.subjects.AsyncSubject;
+
+@SmallTest
+public class LiveDataReactiveStreamsTest {
+    private static final Lifecycle sLifecycle = new Lifecycle() {
+        @Override
+        public void addObserver(LifecycleObserver observer) {
+        }
+
+        @Override
+        public void removeObserver(LifecycleObserver observer) {
+        }
+
+        @Override
+        public State getCurrentState() {
+            return RESUMED;
+        }
+    };
+    private static final LifecycleOwner S_LIFECYCLE_OWNER = new LifecycleOwner() {
+
+        @Override
+        public Lifecycle getLifecycle() {
+            return sLifecycle;
+        }
+
+    };
+
+    private final List<String> mLiveDataOutput = new ArrayList<>();
+    private final Observer<String> mObserver = new Observer<String>() {
+        @Override
+        public void onChanged(@Nullable String s) {
+            mLiveDataOutput.add(s);
+        }
+    };
+
+    private final ReplayProcessor<String> mOutputProcessor = ReplayProcessor.create();
+
+    private static final TestScheduler sBackgroundScheduler = new TestScheduler();
+    private Thread mTestThread;
+
+    @Before
+    public void init() {
+        mTestThread = Thread.currentThread();
+        AppToolkitTaskExecutor.getInstance().setDelegate(new TaskExecutor() {
+
+            @Override
+            public void executeOnDiskIO(Runnable runnable) {
+                throw new IllegalStateException();
+            }
+
+            @Override
+            public void postToMainThread(Runnable runnable) {
+                // Wrong implementation, but it is fine for test
+                runnable.run();
+            }
+
+            @Override
+            public boolean isMainThread() {
+                return Thread.currentThread() == mTestThread;
+            }
+
+        });
+    }
+
+    @After
+    public void removeExecutorDelegate() {
+        AppToolkitTaskExecutor.getInstance().setDelegate(null);
+    }
+
+    @Test
+    public void convertsFromPublisher() {
+        PublishProcessor<String> processor = PublishProcessor.create();
+        LiveData<String> liveData = LiveDataReactiveStreams.fromPublisher(processor);
+
+        liveData.observe(S_LIFECYCLE_OWNER, mObserver);
+
+        processor.onNext("foo");
+        processor.onNext("bar");
+        processor.onNext("baz");
+
+        assertThat(mLiveDataOutput, is(Arrays.asList("foo", "bar", "baz")));
+    }
+
+    @Test
+    public void convertsFromPublisherWithMultipleObservers() {
+        final List<String> output2 = new ArrayList<>();
+        PublishProcessor<String> processor = PublishProcessor.create();
+        LiveData<String> liveData = LiveDataReactiveStreams.fromPublisher(processor);
+
+        liveData.observe(S_LIFECYCLE_OWNER, mObserver);
+
+        processor.onNext("foo");
+        processor.onNext("bar");
+
+        // The second mObserver should only get the newest value and any later values.
+        liveData.observe(S_LIFECYCLE_OWNER, new Observer<String>() {
+            @Override
+            public void onChanged(@Nullable String s) {
+                output2.add(s);
+            }
+        });
+
+        processor.onNext("baz");
+
+        assertThat(mLiveDataOutput, is(Arrays.asList("foo", "bar", "baz")));
+        assertThat(output2, is(Arrays.asList("bar", "baz")));
+    }
+
+    @Test
+    public void convertsFromAsyncPublisher() {
+        Flowable<String> input = Flowable.just("foo")
+                .concatWith(Flowable.just("bar", "baz").observeOn(sBackgroundScheduler));
+        LiveData<String> liveData = LiveDataReactiveStreams.fromPublisher(input);
+
+        liveData.observe(S_LIFECYCLE_OWNER, mObserver);
+
+        assertThat(mLiveDataOutput, is(Collections.singletonList("foo")));
+        sBackgroundScheduler.triggerActions();
+        assertThat(mLiveDataOutput, is(Arrays.asList("foo", "bar", "baz")));
+    }
+
+    @Test
+    public void convertsToPublisherWithSyncData() {
+        MutableLiveData<String> liveData = new MutableLiveData<>();
+        liveData.setValue("foo");
+        assertThat(liveData.getValue(), is("foo"));
+
+        Flowable.fromPublisher(LiveDataReactiveStreams.toPublisher(S_LIFECYCLE_OWNER, liveData))
+                .subscribe(mOutputProcessor);
+
+        liveData.setValue("bar");
+        liveData.setValue("baz");
+
+        assertThat(
+                mOutputProcessor.getValues(new String[]{}),
+                is(new String[] {"foo", "bar", "baz"}));
+    }
+
+    @Test
+    public void convertingToPublisherIsCancelable() {
+        MutableLiveData<String> liveData = new MutableLiveData<>();
+        liveData.setValue("foo");
+        assertThat(liveData.getValue(), is("foo"));
+
+        Disposable disposable = Flowable
+                .fromPublisher(LiveDataReactiveStreams.toPublisher(S_LIFECYCLE_OWNER, liveData))
+                .subscribe(new Consumer<String>() {
+                    @Override
+                    public void accept(String s) throws Exception {
+                        mLiveDataOutput.add(s);
+                    }
+                });
+
+        liveData.setValue("bar");
+        liveData.setValue("baz");
+
+        assertThat(liveData.hasObservers(), is(true));
+        disposable.dispose();
+
+        liveData.setValue("fizz");
+        liveData.setValue("buzz");
+
+        assertThat(mLiveDataOutput, is(Arrays.asList("foo", "bar", "baz")));
+        // Canceling disposable should also remove livedata mObserver.
+        assertThat(liveData.hasObservers(), is(false));
+    }
+
+    @Test
+    public void convertsToPublisherWithBackpressure() {
+        MutableLiveData<String> liveData = new MutableLiveData<>();
+
+        final AsyncSubject<Subscription> subscriptionSubject = AsyncSubject.create();
+
+        Flowable.fromPublisher(LiveDataReactiveStreams.toPublisher(S_LIFECYCLE_OWNER, liveData))
+                .subscribe(new Subscriber<String>() {
+                    @Override
+                    public void onSubscribe(Subscription s) {
+                        subscriptionSubject.onNext(s);
+                        subscriptionSubject.onComplete();
+                    }
+
+                    @Override
+                    public void onNext(String s) {
+                        mOutputProcessor.onNext(s);
+                    }
+
+                    @Override
+                    public void onError(Throwable t) {
+                        throw new RuntimeException(t);
+                    }
+
+                    @Override
+                    public void onComplete() {
+                    }
+                });
+
+        // Subscription should have happened synchronously. If it didn't, this will deadlock.
+        final Subscription subscription = subscriptionSubject.blockingSingle();
+
+        subscription.request(1);
+        assertThat(mOutputProcessor.getValues(new String[]{}), is(new String[] {}));
+
+        liveData.setValue("foo");
+        assertThat(mOutputProcessor.getValues(new String[]{}), is(new String[] {"foo"}));
+
+        subscription.request(2);
+        liveData.setValue("baz");
+        liveData.setValue("fizz");
+
+        assertThat(
+                mOutputProcessor.getValues(new String[]{}),
+                is(new String[] {"foo", "baz", "fizz"}));
+
+        // 'nyan' will be dropped as there is nothing currently requesting a stream.
+        liveData.setValue("nyan");
+        liveData.setValue("cat");
+
+        assertThat(
+                mOutputProcessor.getValues(new String[]{}),
+                is(new String[] {"foo", "baz", "fizz"}));
+
+        // When a new request comes in, the latest value will be pushed.
+        subscription.request(1);
+        assertThat(
+                mOutputProcessor.getValues(new String[]{}),
+                is(new String[] {"foo", "baz", "fizz", "cat"}));
+    }
+
+    @Test
+    public void convertsToPublisherWithAsyncData() {
+        MutableLiveData<String> liveData = new MutableLiveData<>();
+
+        Flowable.fromPublisher(LiveDataReactiveStreams.toPublisher(S_LIFECYCLE_OWNER, liveData))
+                .observeOn(sBackgroundScheduler)
+                .subscribe(mOutputProcessor);
+
+        liveData.setValue("foo");
+
+        assertThat(mOutputProcessor.getValues(new String[]{}), is(new String[] {}));
+        sBackgroundScheduler.triggerActions();
+        assertThat(mOutputProcessor.getValues(new String[]{}), is(new String[] {"foo"}));
+
+        liveData.setValue("bar");
+        liveData.setValue("baz");
+
+        assertThat(mOutputProcessor.getValues(new String[]{}), is(new String[] {"foo"}));
+        sBackgroundScheduler.triggerActions();
+        assertThat(mOutputProcessor.getValues(
+                new String[]{}),
+                is(new String[] {"foo", "bar", "baz"}));
+    }
+}
diff --git a/lifecycle/runtime/.gitignore b/lifecycle/runtime/.gitignore
new file mode 100644
index 0000000..796b96d
--- /dev/null
+++ b/lifecycle/runtime/.gitignore
@@ -0,0 +1 @@
+/build
diff --git a/lifecycle/runtime/build.gradle b/lifecycle/runtime/build.gradle
new file mode 100644
index 0000000..c0c2033
--- /dev/null
+++ b/lifecycle/runtime/build.gradle
@@ -0,0 +1,48 @@
+apply plugin: 'com.android.library'
+apply plugin: 'maven'
+
+android {
+    compileSdkVersion tools.current_sdk
+    buildToolsVersion tools.build_tools_version
+
+    defaultConfig {
+        minSdkVersion flatfoot.min_sdk
+        targetSdkVersion tools.current_sdk
+        versionCode 1
+        versionName "1.0"
+
+        testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
+    }
+
+    buildTypes.all {
+        consumerProguardFiles 'proguard-rules.pro'
+    }
+
+    buildTypes {
+        release {
+            minifyEnabled false
+        }
+    }
+
+    testOptions {
+        unitTests.returnDefaultValues = true
+    }
+}
+
+dependencies {
+    compile project(":lifecycle:common")
+    compile project(":arch:runtime")
+    // necessary for IJ to resolve dependencies.
+    compile libs.support.annotations
+
+    testCompile libs.junit
+    testCompile libs.mockito_core
+
+    testCompile(libs.test_runner) {
+        exclude module: 'support-annotations'
+    }
+}
+
+createAndroidCheckstyle(project)
+
+archivesBaseName = "runtime"
diff --git a/lifecycle/runtime/proguard-rules.pro b/lifecycle/runtime/proguard-rules.pro
new file mode 100644
index 0000000..25f3e87
--- /dev/null
+++ b/lifecycle/runtime/proguard-rules.pro
@@ -0,0 +1,16 @@
+-keepattributes *Annotation*
+
+-keepclassmembers enum android.arch.lifecycle.Lifecycle$Event {
+    <fields>;
+}
+
+-keep class * implements android.arch.lifecycle.LifecycleObserver {
+}
+
+-keep class * implements android.arch.lifecycle.GenericLifecycleObserver {
+    <init>(...);
+}
+
+-keepclassmembers class ** {
+    @android.arch.lifecycle.OnLifecycleEvent *;
+}
\ No newline at end of file
diff --git a/lifecycle/runtime/src/main/AndroidManifest.xml b/lifecycle/runtime/src/main/AndroidManifest.xml
new file mode 100644
index 0000000..274a076
--- /dev/null
+++ b/lifecycle/runtime/src/main/AndroidManifest.xml
@@ -0,0 +1,20 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  ~ Copyright (C) 2016 The Android Open Source Project
+  ~
+  ~ Licensed under the Apache License, Version 2.0 (the "License");
+  ~ you may not use this file except in compliance with the License.
+  ~ You may obtain a copy of the License at
+  ~
+  ~      http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing, software
+  ~ distributed under the License is distributed on an "AS IS" BASIS,
+  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  ~ See the License for the specific language governing permissions and
+  ~ limitations under the License.
+  -->
+
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+          package="android.arch.lifecycle">
+</manifest>
diff --git a/lifecycle/runtime/src/main/java/android/arch/lifecycle/LifecycleRegistry.java b/lifecycle/runtime/src/main/java/android/arch/lifecycle/LifecycleRegistry.java
new file mode 100644
index 0000000..884df0a
--- /dev/null
+++ b/lifecycle/runtime/src/main/java/android/arch/lifecycle/LifecycleRegistry.java
@@ -0,0 +1,213 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.arch.lifecycle;
+
+import android.arch.core.internal.SafeIterableMap;
+import android.support.annotation.NonNull;
+
+import java.util.Map;
+
+import static android.arch.lifecycle.Lifecycle.Event.ON_CREATE;
+import static android.arch.lifecycle.Lifecycle.Event.ON_DESTROY;
+import static android.arch.lifecycle.Lifecycle.Event.ON_PAUSE;
+import static android.arch.lifecycle.Lifecycle.Event.ON_RESUME;
+import static android.arch.lifecycle.Lifecycle.Event.ON_START;
+import static android.arch.lifecycle.Lifecycle.Event.ON_STOP;
+import static android.arch.lifecycle.Lifecycle.State.CREATED;
+import static android.arch.lifecycle.Lifecycle.State.DESTROYED;
+import static android.arch.lifecycle.Lifecycle.State.INITIALIZED;
+import static android.arch.lifecycle.Lifecycle.State.RESUMED;
+import static android.arch.lifecycle.Lifecycle.State.STARTED;
+
+/**
+ * An implementation of {@link Lifecycle} that can handle multiple observers.
+ * <p>
+ * It is used by Fragments and Support Library Activities. You can also directly use it if you have
+ * a custom LifecycleOwner.
+ */
+@SuppressWarnings("WeakerAccess")
+public class LifecycleRegistry extends Lifecycle {
+
+    /**
+     * Custom list that keeps observers and can handle removals / additions during traversal.
+     */
+    private SafeIterableMap<LifecycleObserver, ObserverWithState> mObserverSet =
+            new SafeIterableMap<>();
+    /**
+     * Current state
+     */
+    private State mState;
+    /**
+     * Latest event that was provided via {@link #handleLifecycleEvent(Event)}.
+     */
+    private Event mLastEvent;
+
+    /**
+     * The provider that owns this Lifecycle.
+     */
+    private final LifecycleOwner mLifecycleOwner;
+
+    /**
+     * Creates a new LifecycleRegistry for the given provider.
+     * <p>
+     * You should usually create this inside your LifecycleOwner class's constructor and hold
+     * onto the same instance.
+     *
+     * @param provider The owner LifecycleOwner
+     */
+    public LifecycleRegistry(@NonNull LifecycleOwner provider) {
+        mLifecycleOwner = provider;
+        mState = INITIALIZED;
+    }
+
+    /**
+     * Only marks the current state as the given value. It doesn't dispatch any event to its
+     * listeners.
+     *
+     * @param state new state
+     */
+    public void markState(State state) {
+        mState = state;
+    }
+
+    /**
+     * Sets the current state and notifies the observers.
+     * <p>
+     * Note that if the {@code currentState} is the same state as the last call to this method,
+     * calling this method has no effect.
+     *
+     * @param event The event that was received
+     */
+    public void handleLifecycleEvent(Lifecycle.Event event) {
+        if (mLastEvent == event) {
+            return;
+        }
+        mLastEvent = event;
+        mState = getStateAfter(event);
+        for (Map.Entry<LifecycleObserver, ObserverWithState> entry : mObserverSet) {
+            entry.getValue().sync();
+        }
+    }
+
+    @Override
+    public void addObserver(LifecycleObserver observer) {
+        ObserverWithState observerWithState = new ObserverWithState(observer);
+        mObserverSet.putIfAbsent(observer, observerWithState);
+        observerWithState.sync();
+    }
+
+    @Override
+    public void removeObserver(LifecycleObserver observer) {
+        // we consciously decided not to send destruction events here in opposition to addObserver.
+        // Our reasons for that:
+        // 1. These events haven't yet happened at all. In contrast to events in addObservers, that
+        // actually occurred but earlier.
+        // 2. There are cases when removeObserver happens as a consequence of some kind of fatal
+        // event. If removeObserver method sends destruction events, then a clean up routine becomes
+        // more cumbersome. More specific example of that is: your LifecycleObserver listens for
+        // a web connection, in the usual routine in OnStop method you report to a server that a
+        // session has just ended and you close the connection. Now let's assume now that you
+        // lost an internet and as a result you removed this observer. If you get destruction
+        // events in removeObserver, you should have a special case in your onStop method that
+        // checks if your web connection died and you shouldn't try to report anything to a server.
+        mObserverSet.remove(observer);
+    }
+
+    /**
+     * The number of observers.
+     *
+     * @return The number of observers.
+     */
+    public int getObserverCount() {
+        return mObserverSet.size();
+    }
+
+    @Override
+    public State getCurrentState() {
+        return mState;
+    }
+
+    static State getStateAfter(Event event) {
+        switch (event) {
+            case ON_CREATE:
+            case ON_STOP:
+                return CREATED;
+            case ON_START:
+            case ON_PAUSE:
+                return STARTED;
+            case ON_RESUME:
+                return RESUMED;
+            case ON_DESTROY:
+                return DESTROYED;
+            case ON_ANY:
+                break;
+        }
+        throw new IllegalArgumentException("Unexpected event value " + event);
+    }
+
+    static Event downEvent(State state) {
+        switch (state) {
+            case INITIALIZED:
+                throw new IllegalArgumentException();
+            case CREATED:
+                return ON_DESTROY;
+            case STARTED:
+                return ON_STOP;
+            case RESUMED:
+                return ON_PAUSE;
+            case DESTROYED:
+                throw new IllegalArgumentException();
+        }
+        throw new IllegalArgumentException("Unexpected state value " + state);
+    }
+
+    static Event upEvent(State state) {
+        switch (state) {
+            case INITIALIZED:
+            case DESTROYED:
+                return ON_CREATE;
+            case CREATED:
+                return ON_START;
+            case STARTED:
+                return ON_RESUME;
+            case RESUMED:
+                throw new IllegalArgumentException();
+        }
+        throw new IllegalArgumentException("Unexpected state value " + state);
+    }
+
+    class ObserverWithState {
+        private State mObserverCurrentState = INITIALIZED;
+        private GenericLifecycleObserver mCallback;
+
+        ObserverWithState(LifecycleObserver observer) {
+            mCallback = Lifecycling.getCallback(observer);
+        }
+
+        void sync() {
+            if (mState == DESTROYED && mObserverCurrentState == INITIALIZED) {
+                mObserverCurrentState = DESTROYED;
+            }
+            while (mObserverCurrentState != mState) {
+                Event event = mObserverCurrentState.isAtLeast(mState)
+                        ? downEvent(mObserverCurrentState) : upEvent(mObserverCurrentState);
+                mObserverCurrentState = getStateAfter(event);
+                mCallback.onStateChanged(mLifecycleOwner, event);
+            }
+        }
+    }
+}
diff --git a/lifecycle/runtime/src/main/java/android/arch/lifecycle/LifecycleRegistryOwner.java b/lifecycle/runtime/src/main/java/android/arch/lifecycle/LifecycleRegistryOwner.java
new file mode 100644
index 0000000..634cfc3
--- /dev/null
+++ b/lifecycle/runtime/src/main/java/android/arch/lifecycle/LifecycleRegistryOwner.java
@@ -0,0 +1,31 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.arch.lifecycle;
+
+/**
+ * Specialization of {@link LifecycleOwner} that explicitly returns {@link LifecycleRegistry}.
+ * <p>
+ * This method may be used if an object which updates state of {@link Lifecycle} doesn't own it.
+ * <p>
+ * This class is a temporary implementation detail until Lifecycles are integrated with support
+ * library.
+ */
+@SuppressWarnings({"WeakerAccess", "unused"})
+public interface LifecycleRegistryOwner extends LifecycleOwner {
+    @Override
+    LifecycleRegistry getLifecycle();
+}
diff --git a/lifecycle/runtime/src/main/java/android/arch/lifecycle/ReportFragment.java b/lifecycle/runtime/src/main/java/android/arch/lifecycle/ReportFragment.java
new file mode 100644
index 0000000..490028c
--- /dev/null
+++ b/lifecycle/runtime/src/main/java/android/arch/lifecycle/ReportFragment.java
@@ -0,0 +1,129 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.arch.lifecycle;
+
+import android.app.Activity;
+import android.app.Fragment;
+import android.os.Bundle;
+import android.support.annotation.RestrictTo;
+
+/**
+ * Internal class that dispatches initialization events.
+ *
+ * @hide
+ */
+@RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
+public class ReportFragment extends Fragment {
+
+    private static final String REPORT_FRAGMENT_TAG = "android.arch.lifecycle"
+            + ".LifecycleDispatcher.report_fragment_tag";
+
+    public static void injectIfNeededIn(Activity activity) {
+        // ProcessLifecycleOwner should always correctly work and some activities may not extend
+        // FragmentActivity from support lib, so we use framework fragments for activities
+        android.app.FragmentManager manager = activity.getFragmentManager();
+        if (manager.findFragmentByTag(REPORT_FRAGMENT_TAG) == null) {
+            manager.beginTransaction().add(new ReportFragment(), REPORT_FRAGMENT_TAG).commit();
+            // Hopefully, we are the first to make a transaction.
+            manager.executePendingTransactions();
+        }
+    }
+
+    static ReportFragment get(Activity activity) {
+        return (ReportFragment) activity.getFragmentManager().findFragmentByTag(
+                REPORT_FRAGMENT_TAG);
+    }
+
+    private ActivityInitializationListener mProcessListener;
+
+    private void dispatchCreate(ActivityInitializationListener listener) {
+        if (listener != null) {
+            listener.onCreate();
+        }
+    }
+
+    private void dispatchStart(ActivityInitializationListener listener) {
+        if (listener != null) {
+            listener.onStart();
+        }
+    }
+
+    private void dispatchResume(ActivityInitializationListener listener) {
+        if (listener != null) {
+            listener.onResume();
+        }
+    }
+
+    @Override
+    public void onActivityCreated(Bundle savedInstanceState) {
+        super.onActivityCreated(savedInstanceState);
+        dispatchCreate(mProcessListener);
+        dispatch(Lifecycle.Event.ON_CREATE);
+    }
+
+    @Override
+    public void onStart() {
+        super.onStart();
+        dispatchStart(mProcessListener);
+        dispatch(Lifecycle.Event.ON_START);
+    }
+
+    @Override
+    public void onResume() {
+        super.onResume();
+        dispatchResume(mProcessListener);
+        dispatch(Lifecycle.Event.ON_RESUME);
+    }
+
+    @Override
+    public void onPause() {
+        super.onPause();
+        dispatch(Lifecycle.Event.ON_PAUSE);
+    }
+
+    @Override
+    public void onStop() {
+        super.onStop();
+        dispatch(Lifecycle.Event.ON_STOP);
+    }
+
+    @Override
+    public void onDestroy() {
+        super.onDestroy();
+        dispatch(Lifecycle.Event.ON_DESTROY);
+        // just want to be sure that we won't leak reference to an activity
+        mProcessListener = null;
+    }
+
+    private void dispatch(Lifecycle.Event event) {
+        if (getActivity() instanceof LifecycleRegistryOwner) {
+            ((LifecycleRegistryOwner) getActivity()).getLifecycle().handleLifecycleEvent(event);
+        }
+    }
+
+    void setProcessListener(ActivityInitializationListener processListener) {
+        mProcessListener = processListener;
+    }
+
+    interface ActivityInitializationListener {
+        void onCreate();
+
+        void onStart();
+
+        void onResume();
+    }
+}
diff --git a/lifecycle/runtime/src/test/java/android/arch/lifecycle/LifecycleRegistryTest.java b/lifecycle/runtime/src/test/java/android/arch/lifecycle/LifecycleRegistryTest.java
new file mode 100644
index 0000000..124bd68
--- /dev/null
+++ b/lifecycle/runtime/src/test/java/android/arch/lifecycle/LifecycleRegistryTest.java
@@ -0,0 +1,248 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.arch.lifecycle;
+
+import static android.arch.lifecycle.Lifecycle.Event.ON_CREATE;
+import static android.arch.lifecycle.Lifecycle.Event.ON_DESTROY;
+import static android.arch.lifecycle.Lifecycle.Event.ON_PAUSE;
+import static android.arch.lifecycle.Lifecycle.Event.ON_RESUME;
+import static android.arch.lifecycle.Lifecycle.Event.ON_START;
+import static android.arch.lifecycle.Lifecycle.Event.ON_STOP;
+
+import static org.hamcrest.CoreMatchers.is;
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.mockito.Mockito.inOrder;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.reset;
+import static org.mockito.Mockito.spy;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import android.support.test.filters.SmallTest;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+import org.mockito.InOrder;
+
+@RunWith(JUnit4.class)
+@SmallTest
+public class LifecycleRegistryTest {
+    private LifecycleOwner mLifecycleOwner;
+    private Lifecycle mLifecycle;
+    private LifecycleRegistry mRegistry;
+
+    @Before
+    public void init() {
+        mLifecycleOwner = mock(LifecycleOwner.class);
+        mLifecycle = mock(Lifecycle.class);
+        when(mLifecycleOwner.getLifecycle()).thenReturn(mLifecycle);
+        mRegistry = new LifecycleRegistry(mLifecycleOwner);
+    }
+
+    @Test
+    public void addRemove() {
+        LifecycleObserver observer = mock(LifecycleObserver.class);
+        mRegistry.addObserver(observer);
+        assertThat(mRegistry.getObserverCount(), is(1));
+        mRegistry.removeObserver(observer);
+        assertThat(mRegistry.getObserverCount(), is(0));
+    }
+
+    @Test
+    public void addGenericAndObserve() {
+        GenericLifecycleObserver generic = mock(GenericLifecycleObserver.class);
+        mRegistry.addObserver(generic);
+        dispatchEvent(ON_CREATE);
+        verify(generic).onStateChanged(mLifecycleOwner, ON_CREATE);
+        reset(generic);
+        dispatchEvent(ON_CREATE);
+        verify(generic, never()).onStateChanged(mLifecycleOwner, ON_CREATE);
+    }
+
+    @Test
+    public void addRegularClass() {
+        TestObserver testObserver = mock(TestObserver.class);
+        mRegistry.addObserver(testObserver);
+        dispatchEvent(ON_START);
+        verify(testObserver, never()).onStop();
+        dispatchEvent(ON_STOP);
+        verify(testObserver).onStop();
+    }
+
+    @Test
+    public void add2RemoveOne() {
+        TestObserver observer1 = mock(TestObserver.class);
+        TestObserver observer2 = mock(TestObserver.class);
+        TestObserver observer3 = mock(TestObserver.class);
+        mRegistry.addObserver(observer1);
+        mRegistry.addObserver(observer2);
+        mRegistry.addObserver(observer3);
+
+        dispatchEvent(ON_CREATE);
+
+        verify(observer1).onCreate();
+        verify(observer2).onCreate();
+        verify(observer3).onCreate();
+        reset(observer1, observer2, observer3);
+
+        mRegistry.removeObserver(observer2);
+        dispatchEvent(ON_START);
+
+        verify(observer1).onStart();
+        verify(observer2, never()).onStart();
+        verify(observer3).onStart();
+    }
+
+    @Test
+    public void removeWhileTraversing() {
+        final TestObserver observer2 = mock(TestObserver.class);
+        TestObserver observer1 = spy(new TestObserver() {
+            @Override
+            public void onCreate() {
+                mRegistry.removeObserver(observer2);
+            }
+        });
+        mRegistry.addObserver(observer1);
+        mRegistry.addObserver(observer2);
+        dispatchEvent(ON_CREATE);
+        verify(observer2, never()).onCreate();
+        verify(observer1).onCreate();
+    }
+
+    @Test
+    public void constructionDestruction1() {
+        fullyInitializeRegistry();
+        final TestObserver observer = mock(TestObserver.class);
+        mRegistry.addObserver(observer);
+        InOrder constructionVerifier = inOrder(observer);
+        constructionVerifier.verify(observer).onCreate();
+        constructionVerifier.verify(observer).onStart();
+        constructionVerifier.verify(observer).onResume();
+    }
+
+    @Test
+    public void constructionDestruction2() {
+        fullyInitializeRegistry();
+        final TestObserver observer = spy(new TestObserver() {
+            @Override
+            void onStart() {
+                dispatchEvent(ON_PAUSE);
+            }
+        });
+        mRegistry.addObserver(observer);
+        InOrder constructionOrder = inOrder(observer);
+        constructionOrder.verify(observer).onCreate();
+        constructionOrder.verify(observer).onStart();
+        constructionOrder.verify(observer, never()).onResume();
+    }
+
+    @Test
+    public void constructionDestruction3() {
+        fullyInitializeRegistry();
+        final TestObserver observer = spy(new TestObserver() {
+            @Override
+            void onStart() {
+                dispatchEvent(ON_PAUSE);
+                dispatchEvent(ON_STOP);
+                dispatchEvent(ON_DESTROY);
+            }
+        });
+        mRegistry.addObserver(observer);
+        InOrder orderVerifier = inOrder(observer);
+        orderVerifier.verify(observer).onCreate();
+        orderVerifier.verify(observer).onStart();
+        orderVerifier.verify(observer).onStop();
+        orderVerifier.verify(observer).onDestroy();
+        orderVerifier.verify(observer, never()).onResume();
+    }
+
+    @Test
+    public void twoObserversChangingState() {
+        final TestObserver observer1 = spy(new TestObserver() {
+            @Override
+            void onCreate() {
+                dispatchEvent(ON_START);
+            }
+        });
+        final TestObserver observer2 = mock(TestObserver.class);
+        mRegistry.addObserver(observer1);
+        mRegistry.addObserver(observer2);
+        dispatchEvent(ON_CREATE);
+        verify(observer1, times(1)).onCreate();
+        verify(observer2, times(1)).onCreate();
+        verify(observer1, times(1)).onStart();
+        verify(observer2, times(1)).onStart();
+    }
+
+    @Test
+    public void subscribeToDead() {
+        dispatchEvent(ON_CREATE);
+        final TestObserver observer1 = mock(TestObserver.class);
+        mRegistry.addObserver(observer1);
+        verify(observer1).onCreate();
+        dispatchEvent(ON_DESTROY);
+        verify(observer1).onDestroy();
+        final TestObserver observer2 = mock(TestObserver.class);
+        mRegistry.addObserver(observer2);
+        verify(observer2, never()).onCreate();
+        reset(observer1);
+        dispatchEvent(ON_CREATE);
+        verify(observer1).onCreate();
+        verify(observer2).onCreate();
+    }
+
+    private void dispatchEvent(Lifecycle.Event event) {
+        when(mLifecycle.getCurrentState()).thenReturn(LifecycleRegistry.getStateAfter(event));
+        mRegistry.handleLifecycleEvent(event);
+    }
+
+    private void fullyInitializeRegistry() {
+        dispatchEvent(ON_CREATE);
+        dispatchEvent(ON_START);
+        dispatchEvent(ON_RESUME);
+    }
+
+    private abstract class TestObserver implements LifecycleObserver {
+        @OnLifecycleEvent(ON_CREATE)
+        void onCreate() {
+        }
+
+        @OnLifecycleEvent(ON_START)
+        void onStart() {
+        }
+
+        @OnLifecycleEvent(ON_RESUME)
+        void onResume() {
+        }
+
+        @OnLifecycleEvent(ON_PAUSE)
+        void onPause() {
+        }
+
+        @OnLifecycleEvent(ON_STOP)
+        void onStop() {
+        }
+
+        @OnLifecycleEvent(ON_DESTROY)
+        void onDestroy() {
+        }
+    }
+}
diff --git a/makeFlatfootRepo.sh b/makeFlatfootRepo.sh
new file mode 100755
index 0000000..1e5e817
--- /dev/null
+++ b/makeFlatfootRepo.sh
@@ -0,0 +1,3 @@
+# temporary script to make flatfoot repo, next to the support repo
+cd lifecycle && ./gradlew uploadArchives --info
+cd ../room && ./gradlew uploadArchives --info
diff --git a/media-compat/api21/android/support/v4/media/session/MediaSessionCompatApi21.java b/media-compat/api21/android/support/v4/media/session/MediaSessionCompatApi21.java
index ad9727d..698e37d 100644
--- a/media-compat/api21/android/support/v4/media/session/MediaSessionCompatApi21.java
+++ b/media-compat/api21/android/support/v4/media/session/MediaSessionCompatApi21.java
@@ -167,6 +167,7 @@
         void onStop();
         void onSeekTo(long position);
         void onSetRating(Object ratingObject);
+        void onSetRating(Object ratingObject, Bundle extras);
         void onCustomAction(String action, Bundle extras);
     }
 
diff --git a/media-compat/java/android/support/v4/media/MediaBrowserCompat.java b/media-compat/java/android/support/v4/media/MediaBrowserCompat.java
index 93ee3e0..3c62f94 100644
--- a/media-compat/java/android/support/v4/media/MediaBrowserCompat.java
+++ b/media-compat/java/android/support/v4/media/MediaBrowserCompat.java
@@ -945,9 +945,10 @@
         void subscribe(@NonNull String parentId, Bundle options,
                 @NonNull SubscriptionCallback callback);
         void unsubscribe(@NonNull String parentId, SubscriptionCallback callback);
-        void getItem(final @NonNull String mediaId, @NonNull final ItemCallback cb);
+        void getItem(@NonNull String mediaId, @NonNull ItemCallback cb);
         void search(@NonNull String query, Bundle extras, @NonNull SearchCallback callback);
-        void sendCustomAction(String action, Bundle extras, final CustomActionCallback callback);
+        void sendCustomAction(@NonNull String action, Bundle extras,
+                @Nullable CustomActionCallback callback);
     }
 
     interface MediaBrowserServiceCallbackImpl {
@@ -1289,12 +1290,14 @@
             } catch (RemoteException e) {
                 Log.i(TAG, "Remote error sending a custom action: action=" + action + ", extras="
                         + extras, e);
-                mHandler.post(new Runnable() {
-                    @Override
-                    public void run() {
-                        callback.onError(action, extras, null);
-                    }
-                });
+                if (callback != null) {
+                    mHandler.post(new Runnable() {
+                        @Override
+                        public void run() {
+                            callback.onError(action, extras, null);
+                        }
+                    });
+                }
             }
         }
 
@@ -1803,8 +1806,8 @@
         }
 
         @Override
-        public void sendCustomAction(final String action, final Bundle extras,
-                final CustomActionCallback callback) {
+        public void sendCustomAction(@NonNull final String action, final Bundle extras,
+                @Nullable final CustomActionCallback callback) {
             if (!isConnected()) {
                 throw new IllegalStateException("Cannot send a custom action (" + action + ") with "
                         + "extras " + extras + " because the browser is not connected to the "
@@ -1812,12 +1815,14 @@
             }
             if (mServiceBinderWrapper == null) {
                 Log.i(TAG, "The connected service doesn't support sendCustomAction.");
-                mHandler.post(new Runnable() {
-                    @Override
-                    public void run() {
-                        callback.onError(action, extras, null);
-                    }
-                });
+                if (callback != null) {
+                    mHandler.post(new Runnable() {
+                        @Override
+                        public void run() {
+                            callback.onError(action, extras, null);
+                        }
+                    });
+                }
             }
 
             ResultReceiver receiver = new CustomActionResultReceiver(action, extras, callback,
@@ -1828,12 +1833,14 @@
             } catch (RemoteException e) {
                 Log.i(TAG, "Remote error sending a custom action: action=" + action + ", extras="
                         + extras, e);
-                mHandler.post(new Runnable() {
-                    @Override
-                    public void run() {
-                        callback.onError(action, extras, null);
-                    }
-                });
+                if (callback != null) {
+                    mHandler.post(new Runnable() {
+                        @Override
+                        public void run() {
+                            callback.onError(action, extras, null);
+                        }
+                    });
+                }
             }
         }
 
diff --git a/media-compat/java/android/support/v4/media/session/IMediaSession.aidl b/media-compat/java/android/support/v4/media/session/IMediaSession.aidl
index 2ea696b..3926ac2 100644
--- a/media-compat/java/android/support/v4/media/session/IMediaSession.aidl
+++ b/media-compat/java/android/support/v4/media/session/IMediaSession.aidl
@@ -80,6 +80,7 @@
     void rewind() = 22;
     void seekTo(long pos) = 23;
     void rate(in RatingCompat rating) = 24;
+    void rateWithExtras(in RatingCompat rating, in Bundle extras) = 50;
     void setCaptioningEnabled(boolean enabled) = 45;
     void setRepeatMode(int repeatMode) = 38;
     void setShuffleModeEnabledDeprecated(boolean shuffleMode) = 39;
diff --git a/media-compat/java/android/support/v4/media/session/MediaControllerCompat.java b/media-compat/java/android/support/v4/media/session/MediaControllerCompat.java
index 7e97f7f..b761316 100644
--- a/media-compat/java/android/support/v4/media/session/MediaControllerCompat.java
+++ b/media-compat/java/android/support/v4/media/session/MediaControllerCompat.java
@@ -181,9 +181,9 @@
             case MediaSessionCompat.ACTION_FOLLOW:
             case MediaSessionCompat.ACTION_UNFOLLOW:
                 if (args == null
-                        || !args.containsKey(MediaSessionCompat.ACTION_ARGUMENT_MEDIA_ATTRIBUTE)) {
+                        || !args.containsKey(MediaSessionCompat.ARGUMENT_MEDIA_ATTRIBUTE)) {
                     throw new IllegalArgumentException("An extra field "
-                            + MediaSessionCompat.ACTION_ARGUMENT_MEDIA_ATTRIBUTE + " is required "
+                            + MediaSessionCompat.ARGUMENT_MEDIA_ATTRIBUTE + " is required "
                             + "for this action " + action + ".");
                 }
                 break;
@@ -241,7 +241,7 @@
     }
 
     /**
-     * Get a {@link TransportControls} instance for this session.
+     * Gets a {@link TransportControls} instance for this session.
      *
      * @return A controls instance
      */
@@ -250,7 +250,7 @@
     }
 
     /**
-     * Send the specified media button event to the session. Only media keys can
+     * Sends the specified media button event to the session. Only media keys can
      * be sent by this method, other keys will be ignored.
      *
      * @param keyEvent The media button event to dispatch.
@@ -264,7 +264,7 @@
     }
 
     /**
-     * Get the current playback state for this session.
+     * Gets the current playback state for this session.
      *
      * @return The current PlaybackState or null
      */
@@ -273,7 +273,7 @@
     }
 
     /**
-     * Get the current metadata for this session.
+     * Gets the current metadata for this session.
      *
      * @return The current MediaMetadata or null.
      */
@@ -282,7 +282,7 @@
     }
 
     /**
-     * Get the current play queue for this session if one is set. If you only
+     * Gets the current play queue for this session if one is set. If you only
      * care about the current item {@link #getMetadata()} should be used.
      *
      * @return The current play queue or null.
@@ -292,7 +292,7 @@
     }
 
     /**
-     * Add a queue item from the given {@code description} at the end of the play queue
+     * Adds a queue item from the given {@code description} at the end of the play queue
      * of this session. Not all sessions may support this. To know whether the session supports
      * this, get the session's flags with {@link #getFlags()} and check that the flag
      * {@link MediaSessionCompat#FLAG_HANDLES_QUEUE_COMMANDS} is set.
@@ -308,7 +308,7 @@
     }
 
     /**
-     * Add a queue item from the given {@code description} at the specified position
+     * Adds a queue item from the given {@code description} at the specified position
      * in the play queue of this session. Shifts the queue item currently at that position
      * (if any) and any subsequent queue items to the right (adds one to their indices).
      * Not all sessions may support this. To know whether the session supports this,
@@ -328,7 +328,7 @@
     }
 
     /**
-     * Remove the first occurrence of the specified {@link MediaSessionCompat.QueueItem}
+     * Removes the first occurrence of the specified {@link MediaSessionCompat.QueueItem}
      * with the given {@link MediaDescriptionCompat description} in the play queue of the
      * associated session. Not all sessions may support this. To know whether the session supports
      * this, get the session's flags with {@link #getFlags()} and check that the flag
@@ -345,7 +345,7 @@
     }
 
     /**
-     * Remove an queue item at the specified position in the play queue
+     * Removes an queue item at the specified position in the play queue
      * of this session. Not all sessions may support this. To know whether the session supports
      * this, get the session's flags with {@link #getFlags()} and check that the flag
      * {@link MediaSessionCompat#FLAG_HANDLES_QUEUE_COMMANDS} is set.
@@ -368,21 +368,21 @@
     }
 
     /**
-     * Get the queue title for this session.
+     * Gets the queue title for this session.
      */
     public CharSequence getQueueTitle() {
         return mImpl.getQueueTitle();
     }
 
     /**
-     * Get the extras for this session.
+     * Gets the extras for this session.
      */
     public Bundle getExtras() {
         return mImpl.getExtras();
     }
 
     /**
-     * Get the rating type supported by the session. One of:
+     * Gets the rating type supported by the session. One of:
      * <ul>
      * <li>{@link RatingCompat#RATING_NONE}</li>
      * <li>{@link RatingCompat#RATING_HEART}</li>
@@ -400,7 +400,7 @@
     }
 
     /**
-     * Return whether captioning is enabled for this session.
+     * Returns whether captioning is enabled for this session.
      *
      * @return {@code true} if captioning is enabled, {@code false} if disabled or not set.
      */
@@ -409,7 +409,7 @@
     }
 
     /**
-     * Get the repeat mode for this session.
+     * Gets the repeat mode for this session.
      *
      * @return The latest repeat mode set to the session, or
      *         {@link PlaybackStateCompat#REPEAT_MODE_NONE} if not set.
@@ -419,7 +419,7 @@
     }
 
     /**
-     * Return whether the shuffle mode is enabled for this session.
+     * Returns whether the shuffle mode is enabled for this session.
      *
      * @return {@code true} if the shuffle mode is enabled, {@code false} if disabled or not set.
      * @deprecated Use {@link #getShuffleMode} instead.
@@ -430,7 +430,7 @@
     }
 
     /**
-     * Get the shuffle mode for this session.
+     * Gets the shuffle mode for this session.
      *
      * @return The latest shuffle mode set to the session, or
      *         {@link PlaybackStateCompat#SHUFFLE_MODE_NONE} if not set.
@@ -440,7 +440,7 @@
     }
 
     /**
-     * Get the flags for this session. Flags are defined in
+     * Gets the flags for this session. Flags are defined in
      * {@link MediaSessionCompat}.
      *
      * @return The current set of flags for the session.
@@ -450,7 +450,7 @@
     }
 
     /**
-     * Get the current playback info for this session.
+     * Gets the current playback info for this session.
      *
      * @return The current playback info or null.
      */
@@ -459,7 +459,7 @@
     }
 
     /**
-     * Get an intent for launching UI associated with this session if one
+     * Gets an intent for launching UI associated with this session if one
      * exists.
      *
      * @return A {@link PendingIntent} to launch UI or null.
@@ -469,7 +469,7 @@
     }
 
     /**
-     * Get the token for the session this controller is connected to.
+     * Gets the token for the session this controller is connected to.
      *
      * @return The session's token.
      */
@@ -478,7 +478,7 @@
     }
 
     /**
-     * Set the volume of the output this session is playing on. The command will
+     * Sets the volume of the output this session is playing on. The command will
      * be ignored if it does not support
      * {@link VolumeProviderCompat#VOLUME_CONTROL_ABSOLUTE}. The flags in
      * {@link AudioManager} may be used to affect the handling.
@@ -493,7 +493,7 @@
     }
 
     /**
-     * Adjust the volume of the output this session is playing on. The direction
+     * Adjusts the volume of the output this session is playing on. The direction
      * must be one of {@link AudioManager#ADJUST_LOWER},
      * {@link AudioManager#ADJUST_RAISE}, or {@link AudioManager#ADJUST_SAME}.
      * The command will be ignored if the session does not support
@@ -538,7 +538,7 @@
     }
 
     /**
-     * Stop receiving updates on the specified callback. If an update has
+     * Stops receiving updates on the specified callback. If an update has
      * already been posted you may still receive it after calling this method.
      *
      * @param callback The callback to remove
@@ -567,7 +567,7 @@
     }
 
     /**
-     * Get the session owner's package name.
+     * Gets the session owner's package name.
      *
      * @return The package name of of the session owner.
      */
@@ -665,7 +665,7 @@
         }
 
         /**
-         * Override to handle chagnes to the {@link MediaSessionCompat} extras.
+         * Override to handle changes to the {@link MediaSessionCompat} extras.
          *
          * @param extras The extras that can include other information
          *            associated with the {@link MediaSessionCompat}.
@@ -728,7 +728,7 @@
         }
 
         /**
-         * Set the handler to use for pre 21 callbacks.
+         * Sets the handler to use for pre 21 callbacks.
          */
         private void setHandler(Handler handler) {
             mHandler = new MessageHandler(handler.getLooper());
@@ -1025,7 +1025,7 @@
         public abstract void playFromUri(Uri uri, Bundle extras);
 
         /**
-         * Play an item with a specific id in the play queue. If you specify an
+         * Plays an item with a specific id in the play queue. If you specify an
          * id that is not in the play queue, the behavior is undefined.
          */
         public abstract void skipToQueueItem(long id);
@@ -1043,52 +1043,66 @@
         public abstract void stop();
 
         /**
-         * Move to a new location in the media stream.
+         * Moves to a new location in the media stream.
          *
          * @param pos Position to move to, in milliseconds.
          */
         public abstract void seekTo(long pos);
 
         /**
-         * Start fast forwarding. If playback is already fast forwarding this
+         * Starts fast forwarding. If playback is already fast forwarding this
          * may increase the rate.
          */
         public abstract void fastForward();
 
         /**
-         * Skip to the next item.
+         * Skips to the next item.
          */
         public abstract void skipToNext();
 
         /**
-         * Start rewinding. If playback is already rewinding this may increase
+         * Starts rewinding. If playback is already rewinding this may increase
          * the rate.
          */
         public abstract void rewind();
 
         /**
-         * Skip to the previous item.
+         * Skips to the previous item.
          */
         public abstract void skipToPrevious();
 
         /**
-         * Rate the current content. This will cause the rating to be set for
-         * the current user. The Rating type must match the type returned by
-         * {@link #getRatingType()}.
+         * Rates the current content. This will cause the rating to be set for
+         * the current user. The rating type of the given {@link RatingCompat} must match the type
+         * returned by {@link #getRatingType()}.
          *
          * @param rating The rating to set for the current content
          */
         public abstract void setRating(RatingCompat rating);
 
         /**
-         * Enable/disable captioning for this session.
+         * Rates a media item. This will cause the rating to be set for
+         * the specific media item. The rating type of the given {@link RatingCompat} must match
+         * the type returned by {@link #getRatingType()}.
+         *
+         * @param rating The rating to set for the media item.
+         * @param extras Optional arguments that can include information about the media item
+         *               to be rated.
+         *
+         * @see MediaSessionCompat#ARGUMENT_MEDIA_ATTRIBUTE
+         * @see MediaSessionCompat#ARGUMENT_MEDIA_ATTRIBUTE_VALUE
+         */
+        public abstract void setRating(RatingCompat rating, Bundle extras);
+
+        /**
+         * Enables/disables captioning for this session.
          *
          * @param enabled {@code true} to enable captioning, {@code false} to disable.
          */
         public abstract void setCaptioningEnabled(boolean enabled);
 
         /**
-         * Set the repeat mode for this session.
+         * Sets the repeat mode for this session.
          *
          * @param repeatMode The repeat mode. Must be one of the followings:
          *                   {@link PlaybackStateCompat#REPEAT_MODE_NONE},
@@ -1099,7 +1113,7 @@
         public abstract void setRepeatMode(@PlaybackStateCompat.RepeatMode int repeatMode);
 
         /**
-         * Set the shuffle mode for this session.
+         * Sets the shuffle mode for this session.
          *
          * @param enabled {@code true} to enable the shuffle mode, {@code false} to disable.
          * @deprecated Use {@link #setShuffleMode} instead.
@@ -1108,7 +1122,7 @@
         public abstract void setShuffleModeEnabled(boolean enabled);
 
         /**
-         * Set the shuffle mode for this session.
+         * Sets the shuffle mode for this session.
          *
          * @param shuffleMode The shuffle mode. Must be one of the followings:
          *                    {@link PlaybackStateCompat#SHUFFLE_MODE_NONE},
@@ -1118,7 +1132,7 @@
         public abstract void setShuffleMode(@PlaybackStateCompat.ShuffleMode int shuffleMode);
 
         /**
-         * Send a custom action for the {@link MediaSessionCompat} to perform.
+         * Sends a custom action for the {@link MediaSessionCompat} to perform.
          *
          * @param customAction The action to perform.
          * @param args Optional arguments to supply to the
@@ -1128,7 +1142,7 @@
                 Bundle args);
 
         /**
-         * Send the id and args from a custom action for the
+         * Sends the id and args from a custom action for the
          * {@link MediaSessionCompat} to perform.
          *
          * @see #sendCustomAction(PlaybackStateCompat.CustomAction action,
@@ -1175,7 +1189,7 @@
         }
 
         /**
-         * Get the type of volume handling, either local or remote. One of:
+         * Gets the type of volume handling, either local or remote. One of:
          * <ul>
          * <li>{@link PlaybackInfo#PLAYBACK_TYPE_LOCAL}</li>
          * <li>{@link PlaybackInfo#PLAYBACK_TYPE_REMOTE}</li>
@@ -1188,7 +1202,7 @@
         }
 
         /**
-         * Get the stream this is currently controlling volume on. When the volume
+         * Gets the stream this is currently controlling volume on. When the volume
          * type is {@link PlaybackInfo#PLAYBACK_TYPE_REMOTE} this value does not
          * have meaning and should be ignored.
          *
@@ -1200,7 +1214,7 @@
         }
 
         /**
-         * Get the type of volume control that can be used. One of:
+         * Gets the type of volume control that can be used. One of:
          * <ul>
          * <li>{@link VolumeProviderCompat#VOLUME_CONTROL_ABSOLUTE}</li>
          * <li>{@link VolumeProviderCompat#VOLUME_CONTROL_RELATIVE}</li>
@@ -1215,7 +1229,7 @@
         }
 
         /**
-         * Get the maximum volume that may be set for this session.
+         * Gets the maximum volume that may be set for this session.
          *
          * @return The maximum allowed volume where this session is playing.
          */
@@ -1224,7 +1238,7 @@
         }
 
         /**
-         * Get the current volume for this session.
+         * Gets the current volume for this session.
          *
          * @return The current volume where this session is playing.
          */
@@ -1706,6 +1720,15 @@
         }
 
         @Override
+        public void setRating(RatingCompat rating, Bundle extras) {
+            try {
+                mBinder.rateWithExtras(rating, extras);
+            } catch (RemoteException e) {
+                Log.e(TAG, "Dead object in setRating.", e);
+            }
+        }
+
+        @Override
         public void setCaptioningEnabled(boolean enabled) {
             try {
                 mBinder.setCaptioningEnabled(enabled);
@@ -2254,6 +2277,14 @@
         }
 
         @Override
+        public void setRating(RatingCompat rating, Bundle extras) {
+            Bundle bundle = new Bundle();
+            bundle.putParcelable(MediaSessionCompat.ACTION_ARGUMENT_RATING, rating);
+            bundle.putParcelable(MediaSessionCompat.ACTION_ARGUMENT_EXTRAS, extras);
+            sendCustomAction(MediaSessionCompat.ACTION_SET_RATING, bundle);
+        }
+
+        @Override
         public void setCaptioningEnabled(boolean enabled) {
             Bundle bundle = new Bundle();
             bundle.putBoolean(MediaSessionCompat.ACTION_ARGUMENT_CAPTIONING_ENABLED, enabled);
diff --git a/media-compat/java/android/support/v4/media/session/MediaSessionCompat.java b/media-compat/java/android/support/v4/media/session/MediaSessionCompat.java
index 50df4ea..aaf0a62 100644
--- a/media-compat/java/android/support/v4/media/session/MediaSessionCompat.java
+++ b/media-compat/java/android/support/v4/media/session/MediaSessionCompat.java
@@ -115,19 +115,19 @@
     public @interface SessionFlags {}
 
     /**
-     * Set this flag on the session to indicate that it can handle media button
+     * Sets this flag on the session to indicate that it can handle media button
      * events.
      */
     public static final int FLAG_HANDLES_MEDIA_BUTTONS = 1 << 0;
 
     /**
-     * Set this flag on the session to indicate that it handles transport
+     * Sets this flag on the session to indicate that it handles transport
      * control commands through its {@link Callback}.
      */
     public static final int FLAG_HANDLES_TRANSPORT_CONTROLS = 1 << 1;
 
     /**
-     * Set this flag on the session to indicate that it handles queue
+     * Sets this flag on the session to indicate that it handles queue
      * management commands through its {@link Callback}.
      */
     public static final int FLAG_HANDLES_QUEUE_COMMANDS = 1 << 2;
@@ -149,83 +149,71 @@
 
     /**
      * Predefined custom action to follow an artist, album, or playlist. The extra bundle must have
-     * {@link #ACTION_ARGUMENT_MEDIA_ATTRIBUTE} to indicate the type of the follow action. The
+     * {@link #ARGUMENT_MEDIA_ATTRIBUTE} to indicate the type of the follow action. The
      * bundle can also have an optional string argument,
-     * {@link #ACTION_ARGUMENT_MEDIA_ATTRIBUTE_VALUE}, to specify the target to follow (e.g., the
+     * {@link #ARGUMENT_MEDIA_ATTRIBUTE_VALUE}, to specify the target to follow (e.g., the
      * name of the artist to follow). If this argument is omitted, the currently playing media will
      * be the target of the action. Thus, the session must perform the follow action with the
      * current metadata. If there's no specified attribute in the current metadata, the controller
      * must not omit this argument.
      *
-     * @see #ACTION_ARGUMENT_MEDIA_ATTRIBUTE
-     * @see #ACTION_ARGUMENT_MEDIA_ATTRIBUTE_VALUE
+     * @see #ARGUMENT_MEDIA_ATTRIBUTE
+     * @see #ARGUMENT_MEDIA_ATTRIBUTE_VALUE
      * @see Callback#onCustomAction
      */
     public static final String ACTION_FOLLOW = "android.support.v4.media.session.action.FOLLOW";
 
     /**
      * Predefined custom action to unfollow an artist, album, or playlist. The extra bundle must
-     * have {@link #ACTION_ARGUMENT_MEDIA_ATTRIBUTE} to indicate the type of the unfollow action.
+     * have {@link #ARGUMENT_MEDIA_ATTRIBUTE} to indicate the type of the unfollow action.
      * The bundle can also have an optional string argument,
-     * {@link #ACTION_ARGUMENT_MEDIA_ATTRIBUTE_VALUE}, to specify the target to unfollow (e.g., the
+     * {@link #ARGUMENT_MEDIA_ATTRIBUTE_VALUE}, to specify the target to unfollow (e.g., the
      * name of the artist to unfollow). If this argument is omitted, the currently playing media
      * will be the target of the action. Thus, the session must perform the unfollow action with the
      * current metadata. If there's no specified attribute in the current metadata, the controller
      * must not omit this argument.
      *
-     * @see #ACTION_ARGUMENT_MEDIA_ATTRIBUTE
-     * @see #ACTION_ARGUMENT_MEDIA_ATTRIBUTE_VALUE
+     * @see #ARGUMENT_MEDIA_ATTRIBUTE
+     * @see #ARGUMENT_MEDIA_ATTRIBUTE_VALUE
      * @see Callback#onCustomAction
      */
     public static final String ACTION_UNFOLLOW = "android.support.v4.media.session.action.UNFOLLOW";
 
     /**
-     * Argument for use with {@link #ACTION_FOLLOW} and {@link #ACTION_UNFOLLOW} indicating the
-     * media attribute of the follow/unfollow action. It should be one of the following:
+     * Argument to indicate the media attribute. It should be one of the following:
      * <ul>
      * <li>{@link #MEDIA_ATTRIBUTE_ARTIST}</li>
      * <li>{@link #MEDIA_ATTRIBUTE_PLAYLIST}</li>
      * <li>{@link #MEDIA_ATTRIBUTE_ALBUM}</li>
      * </ul>
-     *
-     * @see #ACTION_FOLLOW
-     * @see #ACTION_UNFOLLOW
      */
-    public static final String ACTION_ARGUMENT_MEDIA_ATTRIBUTE =
-            "android.support.v4.media.session.action.ARGUMENT_MEDIA_ATTRIBUTE";
+    public static final String ARGUMENT_MEDIA_ATTRIBUTE =
+            "android.support.v4.media.session.ARGUMENT_MEDIA_ATTRIBUTE";
 
     /**
-     * String argument for use with {@link #ACTION_FOLLOW} and {@link #ACTION_UNFOLLOW} indicating
-     * the value of the media attribute of the follow/unfollow action (e.g., the name of the artist
-     * to follow).
-     *
-     * @see #ACTION_FOLLOW
-     * @see #ACTION_UNFOLLOW
+     * String argument to indicate the value of the media attribute (e.g., the name of the artist).
      */
-    public static final String ACTION_ARGUMENT_MEDIA_ATTRIBUTE_VALUE =
-            "android.support.v4.media.session.action.ARGUMENT_MEDIA_ATTRIBUTE_VALUE";
+    public static final String ARGUMENT_MEDIA_ATTRIBUTE_VALUE =
+            "android.support.v4.media.session.ARGUMENT_MEDIA_ATTRIBUTE_VALUE";
 
     /**
-     * The media attribute of the follow action which indicates that the target of the action is an
-     * artist.
+     * The value of {@link #ARGUMENT_MEDIA_ATTRIBUTE} indicating the artist.
      *
-     * @see ACTION_ARGUMENT_MEDIA_ATTRIBUTE
+     * @see ARGUMENT_MEDIA_ATTRIBUTE
      */
     public static final int MEDIA_ATTRIBUTE_ARTIST = 0;
 
     /**
-     * The media attribute of the follow action which indicates that the target of the action is an
-     * album.
+     * The value of {@link #ARGUMENT_MEDIA_ATTRIBUTE} indicating the album.
      *
-     * @see ACTION_ARGUMENT_MEDIA_ATTRIBUTE
+     * @see ARGUMENT_MEDIA_ATTRIBUTE
      */
     public static final int MEDIA_ATTRIBUTE_ALBUM = 1;
 
     /**
-     * The media attribute of the follow action which indicates that the target of the action is a
-     * playlist.
+     * The value of {@link #ARGUMENT_MEDIA_ATTRIBUTE} indicating the playlist.
      *
-     * @see ACTION_ARGUMENT_MEDIA_ATTRIBUTE
+     * @see ARGUMENT_MEDIA_ATTRIBUTE
      */
     public static final int MEDIA_ATTRIBUTE_PLAYLIST = 2;
 
@@ -283,6 +271,12 @@
             "android.support.v4.media.session.action.SET_SHUFFLE_MODE";
 
     /**
+     * Custom action to invoke setRating() with extra fields.
+     */
+    static final String ACTION_SET_RATING =
+            "android.support.v4.media.session.action.SET_RATING";
+
+    /**
      * Argument for use with {@link #ACTION_PREPARE_FROM_MEDIA_ID} indicating media id to play.
      */
     static final String ACTION_ARGUMENT_MEDIA_ID =
@@ -302,6 +296,12 @@
             "android.support.v4.media.session.action.ARGUMENT_URI";
 
     /**
+     * Argument for use with {@link #ACTION_SET_RATING} indicating the rate to be set.
+     */
+    static final String ACTION_ARGUMENT_RATING =
+            "android.support.v4.media.session.action.ARGUMENT_RATING";
+
+    /**
      * Argument for use with various actions indicating extra bundle.
      */
     static final String ACTION_ARGUMENT_EXTRAS =
@@ -435,7 +435,7 @@
     }
 
     /**
-     * Add a callback to receive updates on for the MediaSession. This includes
+     * Adds a callback to receive updates on for the MediaSession. This includes
      * media button and volume events. The caller's thread will be used to post
      * events.
      *
@@ -446,7 +446,7 @@
     }
 
     /**
-     * Set the callback to receive updates for the MediaSession. This includes
+     * Sets the callback to receive updates for the MediaSession. This includes
      * media button and volume events. Set the callback to null to stop
      * receiving events.
      *
@@ -458,7 +458,7 @@
     }
 
     /**
-     * Set an intent for launching UI for this Session. This can be used as a
+     * Sets an intent for launching UI for this Session. This can be used as a
      * quick link to an ongoing media screen. The intent should be for an
      * activity that may be started using
      * {@link Activity#startActivity(Intent)}.
@@ -470,7 +470,7 @@
     }
 
     /**
-     * Set a pending intent for your media button receiver to allow restarting
+     * Sets a pending intent for your media button receiver to allow restarting
      * playback after the session has been stopped. If your app is started in
      * this way an {@link Intent#ACTION_MEDIA_BUTTON} intent will be sent via
      * the pending intent.
@@ -487,7 +487,7 @@
     }
 
     /**
-     * Set any flags for the session.
+     * Sets any flags for the session.
      *
      * @param flags The flags to set for this session.
      */
@@ -496,7 +496,7 @@
     }
 
     /**
-     * Set the stream this session is playing on. This will affect the system's
+     * Sets the stream this session is playing on. This will affect the system's
      * volume handling for this session. If {@link #setPlaybackToRemote} was
      * previously called it will stop receiving volume commands and the system
      * will begin sending volume changes to the appropriate stream.
@@ -510,7 +510,7 @@
     }
 
     /**
-     * Configure this session to use remote volume handling. This must be called
+     * Configures this session to use remote volume handling. This must be called
      * to receive volume button events, otherwise the system will adjust the
      * current stream volume for this session. If {@link #setPlaybackToLocal}
      * was previously called that stream will stop receiving volume changes for
@@ -532,7 +532,7 @@
     }
 
     /**
-     * Set if this session is currently active and ready to receive commands. If
+     * Sets if this session is currently active and ready to receive commands. If
      * set to false your session's controller may not be discoverable. You must
      * set the session to active before it can start receiving media button
      * events or transport commands.
@@ -552,7 +552,7 @@
     }
 
     /**
-     * Get the current active state of this session.
+     * Gets the current active state of this session.
      *
      * @return True if the session is active, false otherwise.
      */
@@ -561,7 +561,7 @@
     }
 
     /**
-     * Send a proprietary event to all MediaControllers listening to this
+     * Sends a proprietary event to all MediaControllers listening to this
      * Session. It's up to the Controller/Session owner to determine the meaning
      * of any events.
      *
@@ -585,7 +585,7 @@
     }
 
     /**
-     * Retrieve a token object that can be used by apps to create a
+     * Retrieves a token object that can be used by apps to create a
      * {@link MediaControllerCompat} for interacting with this session. The
      * owner of the session is responsible for deciding how to distribute these
      * tokens.
@@ -603,7 +603,7 @@
     }
 
     /**
-     * Get a controller for this session. This is a convenience method to avoid
+     * Gets a controller for this session. This is a convenience method to avoid
      * having to cache your own controller in process.
      *
      * @return A controller for this session.
@@ -613,7 +613,7 @@
     }
 
     /**
-     * Update the current playback state.
+     * Updates the current playback state.
      *
      * @param state The current state of playback
      */
@@ -622,7 +622,7 @@
     }
 
     /**
-     * Update the current metadata. New metadata can be created using
+     * Updates the current metadata. New metadata can be created using
      * {@link android.support.v4.media.MediaMetadataCompat.Builder}. This operation may take time
      * proportional to the size of the bitmap to replace large bitmaps with a scaled down copy.
      *
@@ -634,7 +634,7 @@
     }
 
     /**
-     * Update the list of items in the play queue. It is an ordered list and
+     * Updates the list of items in the play queue. It is an ordered list and
      * should contain the current item, and previous or upcoming items if they
      * exist. Specify null if there is no current play queue.
      * <p>
@@ -649,7 +649,7 @@
     }
 
     /**
-     * Set the title of the play queue. The UI should display this title along
+     * Sets the title of the play queue. The UI should display this title along
      * with the play queue itself. e.g. "Play Queue", "Now Playing", or an album
      * name.
      *
@@ -660,7 +660,7 @@
     }
 
     /**
-     * Set the style of rating used by this session. Apps trying to set the
+     * Sets the style of rating used by this session. Apps trying to set the
      * rating should use this style. Must be one of the following:
      * <ul>
      * <li>{@link RatingCompat#RATING_NONE}</li>
@@ -677,7 +677,7 @@
     }
 
     /**
-     * Enable/disable captioning for this session.
+     * Enables/disables captioning for this session.
      *
      * @param enabled {@code true} to enable captioning, {@code false} to disable.
      */
@@ -686,7 +686,7 @@
     }
 
     /**
-     * Set the repeat mode for this session.
+     * Sets the repeat mode for this session.
      * <p>
      * Note that if this method is not called before, {@link MediaControllerCompat#getRepeatMode}
      * will return {@link PlaybackStateCompat#REPEAT_MODE_NONE}.
@@ -702,7 +702,7 @@
     }
 
     /**
-     * Set the shuffle mode for this session.
+     * Sets the shuffle mode for this session.
      * <p>
      * Note that if this method is not called before,
      * {@link MediaControllerCompat#isShuffleModeEnabled} will return {@code false}.
@@ -716,7 +716,7 @@
     }
 
     /**
-     * Set the shuffle mode for this session.
+     * Sets the shuffle mode for this session.
      * <p>
      * Note that if this method is not called before, {@link MediaControllerCompat#getShuffleMode}
      * will return {@link PlaybackStateCompat#SHUFFLE_MODE_NONE}.
@@ -731,7 +731,7 @@
     }
 
     /**
-     * Set some extras that can be associated with the
+     * Sets some extras that can be associated with the
      * {@link MediaSessionCompat}. No assumptions should be made as to how a
      * {@link MediaControllerCompat} will handle these extras. Keys should be
      * fully qualified (e.g. com.example.MY_EXTRA) to avoid conflicts.
@@ -867,7 +867,7 @@
         final Object mCallbackObj;
         private WeakReference<MediaSessionImpl> mSessionImpl;
         private CallbackHandler mCallbackHandler = null;
-        private boolean mMediaPlayPauseKeyHandled;
+        private boolean mMediaPlayPauseKeyPending;
 
         public Callback() {
             if (android.os.Build.VERSION.SDK_INT >= 24) {
@@ -924,42 +924,45 @@
                 case KeyEvent.KEYCODE_MEDIA_PLAY_PAUSE:
                 case KeyEvent.KEYCODE_HEADSETHOOK:
                     if (keyEvent.getRepeatCount() > 0) {
+                        // Consider long-press as a single tap.
+                        handleMediaPlayPauseKeySingleTapIfPending();
+                    } else if (mMediaPlayPauseKeyPending) {
                         mCallbackHandler.removeMessages(
                                 CallbackHandler.MSG_MEDIA_PLAY_PAUSE_KEY_DOUBLE_TAP_TIMEOUT);
-                        if (keyEvent.getRepeatCount() == 1) {
-                            handleMediaPlayPauseKeySingleTapIfUnhandled();
-                        }
-                    } else if (mCallbackHandler.hasMessages(
-                            CallbackHandler.MSG_MEDIA_PLAY_PAUSE_KEY_DOUBLE_TAP_TIMEOUT)) {
-                        mCallbackHandler.removeMessages(
-                                CallbackHandler.MSG_MEDIA_PLAY_PAUSE_KEY_DOUBLE_TAP_TIMEOUT);
+                        mMediaPlayPauseKeyPending = false;
                         PlaybackStateCompat state = impl.getPlaybackState();
                         long validActions = state == null ? 0 : state.getActions();
                         // Consider double tap as the next.
                         if ((validActions & PlaybackStateCompat.ACTION_SKIP_TO_NEXT) != 0) {
                             onSkipToNext();
                         }
-                        mMediaPlayPauseKeyHandled = true;
                     } else {
-                        mMediaPlayPauseKeyHandled = false;
+                        mMediaPlayPauseKeyPending = true;
                         mCallbackHandler.sendEmptyMessageDelayed(
                                 CallbackHandler.MSG_MEDIA_PLAY_PAUSE_KEY_DOUBLE_TAP_TIMEOUT,
                                 ViewConfiguration.getDoubleTapTimeout());
                     }
                     return true;
+                default:
+                    // If another key is pressed within double tap timeout, consider the pending
+                    // pending play/pause as a single tap to handle media keys in order.
+                    handleMediaPlayPauseKeySingleTapIfPending();
+                    break;
             }
             return false;
         }
 
-        private void handleMediaPlayPauseKeySingleTapIfUnhandled() {
-            if (mMediaPlayPauseKeyHandled) {
+        private void handleMediaPlayPauseKeySingleTapIfPending() {
+            if (!mMediaPlayPauseKeyPending) {
                 return;
             }
+            mMediaPlayPauseKeyPending = false;
+            mCallbackHandler.removeMessages(
+                    CallbackHandler.MSG_MEDIA_PLAY_PAUSE_KEY_DOUBLE_TAP_TIMEOUT);
             MediaSessionImpl impl = mSessionImpl.get();
             if (impl == null) {
                 return;
             }
-            mMediaPlayPauseKeyHandled = true;
             PlaybackStateCompat state = impl.getPlaybackState();
             long validActions = state == null ? 0 : state.getActions();
             boolean isPlaying = state != null
@@ -1090,12 +1093,21 @@
         /**
          * Override to handle the item being rated.
          *
-         * @param rating
+         * @param rating The rating being set.
          */
         public void onSetRating(RatingCompat rating) {
         }
 
         /**
+         * Override to handle the item being rated.
+         *
+         * @param rating The rating being set.
+         * @param extras The extras can include information about the media item being rated.
+         */
+        public void onSetRating(RatingCompat rating, Bundle extras) {
+        }
+
+        /**
          * Override to handle requests to enable/disable captioning.
          *
          * @param enabled {@code true} to enable captioning, {@code false} to disable.
@@ -1218,7 +1230,7 @@
             @Override
             public void handleMessage(Message msg) {
                 if (msg.what == MSG_MEDIA_PLAY_PAUSE_KEY_DOUBLE_TAP_TIMEOUT) {
-                    handleMediaPlayPauseKeySingleTapIfUnhandled();
+                    handleMediaPlayPauseKeySingleTapIfPending();
                 }
             }
         }
@@ -1344,6 +1356,11 @@
             }
 
             @Override
+            public void onSetRating(Object ratingObj, Bundle extras) {
+                Callback.this.onSetRating(RatingCompat.fromRating(ratingObj), extras);
+            }
+
+            @Override
             public void onCustomAction(String action, Bundle extras) {
                 if (action.equals(ACTION_PLAY_FROM_URI)) {
                     Uri uri = extras.getParcelable(ACTION_ARGUMENT_URI);
@@ -1375,6 +1392,11 @@
                 } else if (action.equals(ACTION_SET_SHUFFLE_MODE)) {
                     int shuffleMode = extras.getInt(ACTION_ARGUMENT_SHUFFLE_MODE);
                     Callback.this.onSetShuffleMode(shuffleMode);
+                } else if (action.equals(ACTION_SET_RATING)) {
+                    extras.setClassLoader(RatingCompat.class.getClassLoader());
+                    RatingCompat rating = extras.getParcelable(ACTION_ARGUMENT_RATING);
+                    Bundle bundle = extras.getBundle(ACTION_ARGUMENT_EXTRAS);
+                    Callback.this.onSetRating(rating, bundle);
                 } else {
                     Callback.this.onCustomAction(action, extras);
                 }
@@ -1573,7 +1595,7 @@
         private Object mItem;
 
         /**
-         * Create a new {@link MediaSessionCompat.QueueItem}.
+         * Creates a new {@link MediaSessionCompat.QueueItem}.
          *
          * @param description The {@link MediaDescriptionCompat} for this item.
          * @param id An identifier for this item. It must be unique within the
@@ -1601,14 +1623,14 @@
         }
 
         /**
-         * Get the description for this item.
+         * Gets the description for this item.
          */
         public MediaDescriptionCompat getDescription() {
             return mDescription;
         }
 
         /**
-         * Get the queue id for this item.
+         * Gets the queue id for this item.
          */
         public long getQueueId() {
             return mId;
@@ -1626,7 +1648,7 @@
         }
 
         /**
-         * Get the underlying
+         * Gets the underlying
          * {@link android.media.session.MediaSession.QueueItem}.
          * <p>
          * On builds before {@link android.os.Build.VERSION_CODES#LOLLIPOP} null
@@ -2658,6 +2680,11 @@
             }
 
             @Override
+            public void rateWithExtras(RatingCompat rating, Bundle extras) throws RemoteException {
+                postToHandler(MessageHandler.MSG_RATE_EXTRA, rating, extras);
+            }
+
+            @Override
             public void setCaptioningEnabled(boolean enabled) throws RemoteException {
                 postToHandler(MessageHandler.MSG_SET_CAPTIONING_ENABLED, enabled);
             }
@@ -2805,6 +2832,7 @@
             private static final int MSG_REWIND = 17;
             private static final int MSG_SEEK_TO = 18;
             private static final int MSG_RATE = 19;
+            private static final int MSG_RATE_EXTRA = 31;
             private static final int MSG_CUSTOM_ACTION = 20;
             private static final int MSG_MEDIA_BUTTON = 21;
             private static final int MSG_SET_VOLUME = 22;
@@ -2914,6 +2942,9 @@
                     case MSG_RATE:
                         cb.onSetRating((RatingCompat) msg.obj);
                         break;
+                    case MSG_RATE_EXTRA:
+                        cb.onSetRating((RatingCompat) msg.obj, msg.getData());
+                        break;
                     case MSG_CUSTOM_ACTION:
                         cb.onCustomAction((String) msg.obj, msg.getData());
                         break;
@@ -3576,6 +3607,12 @@
             }
 
             @Override
+            public void rateWithExtras(RatingCompat rating, Bundle extras) throws RemoteException {
+                // Will not be called.
+                throw new AssertionError();
+            }
+
+            @Override
             public void setCaptioningEnabled(boolean enabled) throws RemoteException {
                 // Will not be called.
                 throw new AssertionError();
diff --git a/media-compat/lint-baseline.xml b/media-compat/lint-baseline.xml
index 172bbf6..fb44511 100644
--- a/media-compat/lint-baseline.xml
+++ b/media-compat/lint-baseline.xml
@@ -1,4 +1,4 @@
 <?xml version="1.0" encoding="UTF-8"?>
-<issues format="4" by="lint 2.4.0-alpha6">
+<issues format="4" by="lint 3.0.0-alpha5">
 
 </issues>
diff --git a/media-compat/tests/src/android/support/v4/media/MediaBrowserServiceCompatTest.java b/media-compat/tests/src/android/support/v4/media/MediaBrowserServiceCompatTest.java
index 4856cfd..4ceac10 100644
--- a/media-compat/tests/src/android/support/v4/media/MediaBrowserServiceCompatTest.java
+++ b/media-compat/tests/src/android/support/v4/media/MediaBrowserServiceCompatTest.java
@@ -378,7 +378,6 @@
         assertEquals(val, browserRoot.getExtras().getString(key));
     }
 
-
     @Test
     @SmallTest
     public void testDelayedSetSessionToken() throws Exception {
@@ -583,6 +582,5 @@
                 mWaitLock.notify();
             }
         }
-    };
-
+    }
 }
diff --git a/media-compat/tests/src/android/support/v4/media/session/MediaControllerCompatTest.java b/media-compat/tests/src/android/support/v4/media/session/MediaControllerCompatTest.java
index a1a59e3..6103121 100644
--- a/media-compat/tests/src/android/support/v4/media/session/MediaControllerCompatTest.java
+++ b/media-compat/tests/src/android/support/v4/media/session/MediaControllerCompatTest.java
@@ -288,9 +288,17 @@
             assertEquals(rating.getStarRating(), mCallback.mRating.getStarRating(), DELTA);
 
             mCallback.reset();
-            final String mediaId = "test-media-id";
             final Bundle extras = new Bundle();
             extras.putString(EXTRAS_KEY, EXTRAS_VALUE);
+            controls.setRating(rating, extras);
+            mWaitLock.wait(TIME_OUT_MS);
+            assertTrue(mCallback.mOnSetRatingCalled);
+            assertEquals(rating.getRatingStyle(), mCallback.mRating.getRatingStyle());
+            assertEquals(rating.getStarRating(), mCallback.mRating.getStarRating(), DELTA);
+            assertEquals(EXTRAS_VALUE, mCallback.mExtras.getString(EXTRAS_KEY));
+
+            mCallback.reset();
+            final String mediaId = "test-media-id";
             controls.playFromMediaId(mediaId, extras);
             mWaitLock.wait(TIME_OUT_MS);
             assertTrue(mCallback.mOnPlayFromMediaIdCalled);
@@ -613,6 +621,16 @@
         }
 
         @Override
+        public void onSetRating(RatingCompat rating, Bundle extras) {
+            synchronized (mWaitLock) {
+                mOnSetRatingCalled = true;
+                mRating = rating;
+                mExtras = extras;
+                mWaitLock.notify();
+            }
+        }
+
+        @Override
         public void onPlayFromMediaId(String mediaId, Bundle extras) {
             synchronized (mWaitLock) {
                 mOnPlayFromMediaIdCalled = true;
diff --git a/media-compat/tests/src/android/support/v4/media/session/MediaSessionCompatTest.java b/media-compat/tests/src/android/support/v4/media/session/MediaSessionCompatTest.java
index a3744cc..b44a085 100644
--- a/media-compat/tests/src/android/support/v4/media/session/MediaSessionCompatTest.java
+++ b/media-compat/tests/src/android/support/v4/media/session/MediaSessionCompatTest.java
@@ -600,18 +600,8 @@
         PendingIntent pi = PendingIntent.getBroadcast(getContext(), 0, mediaButtonIntent, 0);
         mSession.setMediaButtonReceiver(pi);
 
-        long supportedActions = PlaybackStateCompat.ACTION_PLAY | PlaybackStateCompat.ACTION_PAUSE
-                | PlaybackStateCompat.ACTION_PLAY_PAUSE | PlaybackStateCompat.ACTION_STOP
-                | PlaybackStateCompat.ACTION_SKIP_TO_NEXT
-                | PlaybackStateCompat.ACTION_SKIP_TO_PREVIOUS
-                | PlaybackStateCompat.ACTION_FAST_FORWARD | PlaybackStateCompat.ACTION_REWIND;
-
         // Set state to STATE_PLAYING to get higher priority.
-        PlaybackStateCompat defaultState = new PlaybackStateCompat.Builder()
-                .setActions(supportedActions)
-                .setState(PlaybackStateCompat.STATE_PLAYING, 0L, 0.0f)
-                .build();
-        mSession.setPlaybackState(defaultState);
+        setPlaybackState(PlaybackStateCompat.STATE_PLAYING);
 
         sessionCallback.reset(1);
         sendMediaKeyInputToController(KeyEvent.KEYCODE_MEDIA_PLAY);
@@ -651,24 +641,21 @@
         // Test PLAY_PAUSE button twice.
         // First, send PLAY_PAUSE button event while in STATE_PAUSED.
         sessionCallback.reset(1);
-        mSession.setPlaybackState(new PlaybackStateCompat.Builder().setActions(supportedActions)
-                .setState(PlaybackStateCompat.STATE_PAUSED, 0L, 0.0f).build());
+        setPlaybackState(PlaybackStateCompat.STATE_PAUSED);
         sendMediaKeyInputToController(KeyEvent.KEYCODE_MEDIA_PLAY_PAUSE);
         assertTrue(sessionCallback.await(TIME_OUT_MS));
         assertEquals(1, sessionCallback.mOnPlayCalledCount);
 
         // Next, send PLAY_PAUSE button event while in STATE_PLAYING.
         sessionCallback.reset(1);
-        mSession.setPlaybackState(new PlaybackStateCompat.Builder().setActions(supportedActions)
-                .setState(PlaybackStateCompat.STATE_PLAYING, 0L, 0.0f).build());
+        setPlaybackState(PlaybackStateCompat.STATE_PLAYING);
         sendMediaKeyInputToController(KeyEvent.KEYCODE_MEDIA_PLAY_PAUSE);
         assertTrue(sessionCallback.await(TIME_OUT_MS));
         assertTrue(sessionCallback.mOnPauseCalled);
 
         // Double tap of PLAY_PAUSE is the next track.
         sessionCallback.reset(2);
-        mSession.setPlaybackState(new PlaybackStateCompat.Builder().setActions(supportedActions)
-                .setState(PlaybackStateCompat.STATE_PAUSED, 0L, 0.0f).build());
+        setPlaybackState(PlaybackStateCompat.STATE_PAUSED);
         sendMediaKeyInputToController(KeyEvent.KEYCODE_MEDIA_PLAY_PAUSE);
         sendMediaKeyInputToController(KeyEvent.KEYCODE_MEDIA_PLAY_PAUSE);
         assertFalse(sessionCallback.await(WAIT_TIME_MS));
@@ -679,8 +666,7 @@
         // Test PLAY_PAUSE button long-press.
         // It should be the same as the single short-press.
         sessionCallback.reset(1);
-        mSession.setPlaybackState(new PlaybackStateCompat.Builder().setActions(supportedActions)
-                .setState(PlaybackStateCompat.STATE_PAUSED, 0L, 0.0f).build());
+        setPlaybackState(PlaybackStateCompat.STATE_PAUSED);
         sendMediaKeyInputToController(KeyEvent.KEYCODE_MEDIA_PLAY_PAUSE, true);
         assertTrue(sessionCallback.await(TIME_OUT_MS));
         assertEquals(1, sessionCallback.mOnPlayCalledCount);
@@ -700,16 +686,53 @@
         // Initial long-press of the PLAY_PAUSE is considered as the single short-press already,
         // so it shouldn't be used as the first tap of the double tap.
         sessionCallback.reset(2);
-        mSession.setPlaybackState(new PlaybackStateCompat.Builder().setActions(supportedActions)
-                .setState(PlaybackStateCompat.STATE_PAUSED, 0L, 0.0f).build());
+        setPlaybackState(PlaybackStateCompat.STATE_PAUSED);
         sendMediaKeyInputToController(KeyEvent.KEYCODE_MEDIA_PLAY_PAUSE, true);
         sendMediaKeyInputToController(KeyEvent.KEYCODE_MEDIA_PLAY_PAUSE);
         assertTrue(sessionCallback.await(TIME_OUT_MS));
         // onMediaButtonEvent() calls either onPlay() or onPause() depending on the playback state,
         // so onPlay() should be called twice while onPause() isn't called.
-        assertEquals(2, sessionCallback.mOnPlayCalledCount);
-        assertFalse(sessionCallback.mOnPauseCalled);
+        assertEquals(1, sessionCallback.mOnPlayCalledCount);
+        assertTrue(sessionCallback.mOnPauseCalled);
         assertFalse(sessionCallback.mOnSkipToNextCalled);
+
+        // If another media key is pressed while the double tap of PLAY_PAUSE,
+        // PLAY_PAUSE should be handles as normal.
+        sessionCallback.reset(3);
+        setPlaybackState(PlaybackStateCompat.STATE_PAUSED);
+        sendMediaKeyInputToController(KeyEvent.KEYCODE_MEDIA_PLAY_PAUSE);
+        sendMediaKeyInputToController(KeyEvent.KEYCODE_MEDIA_STOP);
+        sendMediaKeyInputToController(KeyEvent.KEYCODE_MEDIA_PLAY_PAUSE);
+        assertTrue(sessionCallback.await(TIME_OUT_MS));
+        assertFalse(sessionCallback.mOnSkipToNextCalled);
+        assertTrue(sessionCallback.mOnStopCalled);
+        assertEquals(2, sessionCallback.mOnPlayCalledCount);
+
+        // Test if media keys are handled in order.
+        sessionCallback.reset(2);
+        setPlaybackState(PlaybackStateCompat.STATE_PAUSED);
+        sendMediaKeyInputToController(KeyEvent.KEYCODE_MEDIA_PLAY_PAUSE);
+        sendMediaKeyInputToController(KeyEvent.KEYCODE_MEDIA_STOP);
+        assertTrue(sessionCallback.await(TIME_OUT_MS));
+        assertEquals(1, sessionCallback.mOnPlayCalledCount);
+        assertTrue(sessionCallback.mOnStopCalled);
+        synchronized (mWaitLock) {
+            assertEquals(PlaybackStateCompat.STATE_STOPPED,
+                    mSession.getController().getPlaybackState().getState());
+        }
+    }
+
+    private void setPlaybackState(int state) {
+        final long allActions = PlaybackStateCompat.ACTION_PLAY | PlaybackStateCompat.ACTION_PAUSE
+                | PlaybackStateCompat.ACTION_PLAY_PAUSE | PlaybackStateCompat.ACTION_STOP
+                | PlaybackStateCompat.ACTION_SKIP_TO_NEXT
+                | PlaybackStateCompat.ACTION_SKIP_TO_PREVIOUS
+                | PlaybackStateCompat.ACTION_FAST_FORWARD | PlaybackStateCompat.ACTION_REWIND;
+        PlaybackStateCompat playbackState = new PlaybackStateCompat.Builder().setActions(allActions)
+                .setState(state, 0L, 0.0f).build();
+        synchronized (mWaitLock) {
+            mSession.setPlaybackState(playbackState);
+        }
     }
 
     @Test
@@ -995,18 +1018,21 @@
         @Override
         public void onPlay() {
             mOnPlayCalledCount++;
+            setPlaybackState(PlaybackStateCompat.STATE_PLAYING);
             mLatch.countDown();
         }
 
         @Override
         public void onPause() {
             mOnPauseCalled = true;
+            setPlaybackState(PlaybackStateCompat.STATE_PAUSED);
             mLatch.countDown();
         }
 
         @Override
         public void onStop() {
             mOnStopCalled = true;
+            setPlaybackState(PlaybackStateCompat.STATE_STOPPED);
             mLatch.countDown();
         }
 
diff --git a/percent/lint-baseline.xml b/percent/lint-baseline.xml
index 172bbf6..fb44511 100644
--- a/percent/lint-baseline.xml
+++ b/percent/lint-baseline.xml
@@ -1,4 +1,4 @@
 <?xml version="1.0" encoding="UTF-8"?>
-<issues format="4" by="lint 2.4.0-alpha6">
+<issues format="4" by="lint 3.0.0-alpha5">
 
 </issues>
diff --git a/recommendation/lint-baseline.xml b/recommendation/lint-baseline.xml
index 2a79d28..b373b5e 100644
--- a/recommendation/lint-baseline.xml
+++ b/recommendation/lint-baseline.xml
@@ -1,191 +1,15 @@
 <?xml version="1.0" encoding="UTF-8"?>
-<issues format="4" by="lint 2.4.0-alpha6">
-
-    <issue
-        id="MissingSuperCall"
-        message="Overriding method should call `super.onAnimationEnd`"
-        errorLine1="                public void onAnimationEnd(Animation animation) {"
-        errorLine2="                            ~~~~~~~~~~~~~~">
-        <location
-            file="java/android/support/v4/app/FragmentManager.java"
-            line="1604"
-            column="29"/>
-    </issue>
-
-    <issue
-        id="MissingSuperCall"
-        message="Overriding method should call `super.onAnimationEnd`"
-        errorLine1="        public void onAnimationEnd(Animation animation) {"
-        errorLine2="                    ~~~~~~~~~~~~~~">
-        <location
-            file="java/android/support/v4/app/FragmentManager.java"
-            line="3935"
-            column="21"/>
-    </issue>
-
-    <issue
-        id="Range"
-        message="Value must be ≥ 0 (was -1)"
-        errorLine1="                childHeightSpec = MeasureSpec.makeMeasureSpec(maxLayoutHeight, MeasureSpec.AT_MOST);"
-        errorLine2="                                                              ~~~~~~~~~~~~~~~">
-        <location
-            file="java/android/support/v4/widget/SlidingPaneLayout.java"
-            line="536"
-            column="63"/>
-    </issue>
-
-    <issue
-        id="Range"
-        message="Value must be ≥ 0 (was -1)"
-        errorLine1="                childHeightSpec = MeasureSpec.makeMeasureSpec(maxLayoutHeight, MeasureSpec.EXACTLY);"
-        errorLine2="                                                              ~~~~~~~~~~~~~~~">
-        <location
-            file="java/android/support/v4/widget/SlidingPaneLayout.java"
-            line="538"
-            column="63"/>
-    </issue>
-
-    <issue
-        id="Range"
-        message="Value must be ≥ 0 (was -1)"
-        errorLine1="                                childHeightSpec = MeasureSpec.makeMeasureSpec(maxLayoutHeight,"
-        errorLine2="                                                                              ~~~~~~~~~~~~~~~">
-        <location
-            file="java/android/support/v4/widget/SlidingPaneLayout.java"
-            line="586"
-            column="79"/>
-    </issue>
-
-    <issue
-        id="Range"
-        message="Value must be ≥ 0 (was -1)"
-        errorLine1="                                childHeightSpec = MeasureSpec.makeMeasureSpec(maxLayoutHeight,"
-        errorLine2="                                                                              ~~~~~~~~~~~~~~~">
-        <location
-            file="java/android/support/v4/widget/SlidingPaneLayout.java"
-            line="589"
-            column="79"/>
-    </issue>
-
-    <issue
-        id="Range"
-        message="Value must be ≥ 0 (was -1)"
-        errorLine1="                            childHeightSpec = MeasureSpec.makeMeasureSpec(maxLayoutHeight,"
-        errorLine2="                                                                          ~~~~~~~~~~~~~~~">
-        <location
-            file="java/android/support/v4/widget/SlidingPaneLayout.java"
-            line="608"
-            column="75"/>
-    </issue>
-
-    <issue
-        id="Range"
-        message="Value must be ≥ 0 (was -1)"
-        errorLine1="                            childHeightSpec = MeasureSpec.makeMeasureSpec(maxLayoutHeight,"
-        errorLine2="                                                                          ~~~~~~~~~~~~~~~">
-        <location
-            file="java/android/support/v4/widget/SlidingPaneLayout.java"
-            line="611"
-            column="75"/>
-    </issue>
-
-    <issue
-        id="LongLogTag"
-        message="The logging tag can be at most 23 characters, was 25 (FragmentStatePagerAdapter)"
-        errorLine1="        if (DEBUG) Log.v(TAG, &quot;Adding item #&quot; + position + &quot;: f=&quot; + fragment);"
-        errorLine2="                         ~~~">
-        <location
-            file="java/android/support/v4/app/FragmentStatePagerAdapter.java"
-            line="110"
-            column="26"/>
-    </issue>
-
-    <issue
-        id="LongLogTag"
-        message="The logging tag can be at most 23 characters, was 25 (FragmentStatePagerAdapter)"
-        errorLine1="        if (DEBUG) Log.v(TAG, &quot;Removing item #&quot; + position + &quot;: f=&quot; + object"
-        errorLine2="                         ~~~">
-        <location
-            file="java/android/support/v4/app/FragmentStatePagerAdapter.java"
-            line="135"
-            column="26"/>
-    </issue>
-
-    <issue
-        id="LongLogTag"
-        message="The logging tag can be at most 23 characters, was 25 (FragmentStatePagerAdapter)"
-        errorLine1="                        Log.w(TAG, &quot;Bad fragment at key &quot; + key);"
-        errorLine2="                              ~~~">
-        <location
-            file="java/android/support/v4/app/FragmentStatePagerAdapter.java"
-            line="224"
-            column="31"/>
-    </issue>
-
-    <issue
-        id="LongLogTag"
-        message="The logging tag can be at most 23 characters, was 28 (RoundedBitmapDrawableFactory)"
-        errorLine1="            Log.w(TAG, &quot;RoundedBitmapDrawable cannot decode &quot; + filepath);"
-        errorLine2="                  ~~~">
-        <location
-            file="java/android/support/v4/graphics/drawable/RoundedBitmapDrawableFactory.java"
-            line="80"
-            column="19"/>
-    </issue>
-
-    <issue
-        id="LongLogTag"
-        message="The logging tag can be at most 23 characters, was 28 (RoundedBitmapDrawableFactory)"
-        errorLine1="            Log.w(TAG, &quot;RoundedBitmapDrawable cannot decode &quot; + is);"
-        errorLine2="                  ~~~">
-        <location
-            file="java/android/support/v4/graphics/drawable/RoundedBitmapDrawableFactory.java"
-            line="93"
-            column="19"/>
-    </issue>
-
-    <issue
-        id="LongLogTag"
-        message="The logging tag can be at most 23 characters, was 24 (WakefulBroadcastReceiver)"
-        errorLine1="            Log.w(&quot;WakefulBroadcastReceiver&quot;, &quot;No active wake lock id #&quot; + id);"
-        errorLine2="                  ~~~~~~~~~~~~~~~~~~~~~~~~~~">
-        <location
-            file="java/android/support/v4/content/WakefulBroadcastReceiver.java"
-            line="141"
-            column="19"/>
-    </issue>
+<issues format="4" by="lint 3.0.0-alpha5">
 
     <issue
         id="WrongConstant"
-        message="Must be one of: Gravity.LEFT, Gravity.RIGHT, GravityCompat.START, GravityCompat.END"
-        errorLine1="        setDrawerLockMode(lockMode, gravity);"
-        errorLine2="                                    ~~~~~~~">
+        message="Must be one or more of: Gravity.LEFT, Gravity.RIGHT, GravityCompat.START, GravityCompat.END"
+        errorLine1="                            + gravityToString(childGravity) + &quot; but this &quot; + TAG + &quot; already has a &quot;"
+        errorLine2="                                              ~~~~~~~~~~~~">
         <location
             file="java/android/support/v4/widget/DrawerLayout.java"
-            line="680"
-            column="37"/>
-    </issue>
-
-    <issue
-        id="WrongConstant"
-        message="Must be one of: Gravity.LEFT, Gravity.RIGHT, GravityCompat.START, GravityCompat.END"
-        errorLine1="        return getDrawerLockMode(drawerGravity);"
-        errorLine2="                                 ~~~~~~~~~~~~~">
-        <location
-            file="java/android/support/v4/widget/DrawerLayout.java"
-            line="753"
-            column="34"/>
-    </issue>
-
-    <issue
-        id="WrongConstant"
-        message="Flag not allowed here"
-        errorLine1="                        getDrawerViewAbsoluteGravity(child) &amp; Gravity.HORIZONTAL_GRAVITY_MASK;"
-        errorLine2="                        ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
-        <location
-            file="java/android/support/v4/widget/DrawerLayout.java"
-            line="1086"
-            column="25"/>
+            line="1075"
+            column="47"/>
     </issue>
 
 </issues>
diff --git a/room/.gitignore b/room/.gitignore
new file mode 100644
index 0000000..be4e6f1
--- /dev/null
+++ b/room/.gitignore
@@ -0,0 +1,4 @@
+local.properties
+maven-repo/
+build/
+*.DS_Store
diff --git a/room/common/build.gradle b/room/common/build.gradle
new file mode 100644
index 0000000..5b0d53d
--- /dev/null
+++ b/room/common/build.gradle
@@ -0,0 +1,30 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+apply plugin: 'java'
+apply plugin: 'maven'
+
+sourceCompatibility = 1.7
+
+dependencies {
+    compile libs.support.annotations
+    testCompile libs.junit
+    testCompile libs.mockito_core
+}
+
+archivesBaseName = "common"
+
+createAndroidCheckstyle(project)
diff --git a/room/common/src/main/java/android/arch/persistence/room/ColumnInfo.java b/room/common/src/main/java/android/arch/persistence/room/ColumnInfo.java
new file mode 100644
index 0000000..84f5844
--- /dev/null
+++ b/room/common/src/main/java/android/arch/persistence/room/ColumnInfo.java
@@ -0,0 +1,95 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.arch.persistence.room;
+
+import android.support.annotation.IntDef;
+
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+/**
+ * Allows specific customization about the column associated with this field.
+ * <p>
+ * For example, you can specify a column name for the field or change the column's type affinity.
+ */
+@Target(ElementType.FIELD)
+@Retention(RetentionPolicy.CLASS)
+public @interface ColumnInfo {
+    /**
+     * Name of the column in the database. Defaults to the field name if not set.
+     * @return Name of the column in the database.
+     */
+    String name() default INHERIT_FIELD_NAME;
+
+    /**
+     * The type affinity for the column, which will be used when constructing the database.
+     * <p>
+     * If it is not specified, Room resolves it based on the field's type and available
+     * TypeConverters.
+     * <p>
+     * See <a href="https://www.sqlite.org/datatype3.html">SQLite types documentation</a> for
+     * details.
+     *
+     * @return The type affinity of the column.
+     */
+    @SuppressWarnings("unused") @SQLiteTypeAffinity int typeAffinity() default UNDEFINED;
+
+    /**
+     * Convenience method to index the field.
+     * <p>
+     * If you would like to create a composite index instead, see: {@link Index}.
+     *
+     * @return True if this field should be indexed, false otherwise. Defaults to false.
+     */
+    boolean index() default false;
+
+    /**
+     * Constant to let Room inherit the field name as the column name. If used, Room will use the
+     * field name as the column name.
+     */
+    String INHERIT_FIELD_NAME = "[field-name]";
+
+    /**
+     * Undefined type affinity. Will be resolved based on the type.
+     */
+    int UNDEFINED = 1;
+    /**
+     * Column affinity constant for strings.
+     */
+    int TEXT = 2;
+    /**
+     * Column affinity constant for integers or booleans.
+     */
+    int INTEGER = 3;
+    /**
+     * Column affinity constant for floats or doubles.
+     */
+    int REAL = 4;
+    /**
+     * Column affinity constant for binary data.
+     */
+    int BLOB = 5;
+
+    /**
+     * The SQLite column type constants that can be used in {@link #typeAffinity()}
+     */
+    @IntDef({UNDEFINED, TEXT, INTEGER, REAL, BLOB})
+    @interface SQLiteTypeAffinity {
+    }
+}
diff --git a/room/common/src/main/java/android/arch/persistence/room/Dao.java b/room/common/src/main/java/android/arch/persistence/room/Dao.java
new file mode 100644
index 0000000..ea205e6
--- /dev/null
+++ b/room/common/src/main/java/android/arch/persistence/room/Dao.java
@@ -0,0 +1,47 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.arch.persistence.room;
+
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+/**
+ * Marks the class as a Data Access Object.
+ * <p>
+ * Data Access Objects are the main classes where you define your database interactions. They can
+ * include a variety of query methods.
+ * <p>
+ * The class marked with {@code @Dao} should either be an interface or an abstract class. At compile
+ * time, Room will generate an implementation of this class when it is referenced by a
+ * {@link Database}.
+ * <p>
+ * An abstract {@code @Dao} class can optionally have a constructor that takes a {@link Database}
+ * as its only parameter.
+ * <p>
+ * It is recommended to have multiple {@code Dao} classes in your codebase depending on the tables
+ * they touch.
+ *
+ * @see Query
+ * @see Delete
+ * @see Insert
+ */
+@Target(ElementType.TYPE)
+@Retention(RetentionPolicy.CLASS)
+public @interface Dao {
+}
diff --git a/room/common/src/main/java/android/arch/persistence/room/Database.java b/room/common/src/main/java/android/arch/persistence/room/Database.java
new file mode 100644
index 0000000..f12d1b9
--- /dev/null
+++ b/room/common/src/main/java/android/arch/persistence/room/Database.java
@@ -0,0 +1,96 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.arch.persistence.room;
+
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+/**
+ * Marks a class as a RoomDatabase.
+ * <p>
+ * The class should be an abstract class and extend
+ * {@link android.arch.persistence.room.RoomDatabase RoomDatabase}.
+ * <p>
+ * You can receive an implementation of the class via
+ * {@link android.arch.persistence.room.Room#databaseBuilder Room.databaseBuilder} or
+ * {@link android.arch.persistence.room.Room#inMemoryDatabaseBuilder Room.inMemoryDatabaseBuilder}.
+ * <p>
+ * <pre>
+ * // User and Book are classes annotated with {@literal @}Entity.
+ * {@literal @}Database(version = 1, entities = {User.class, Book.class})
+ * abstract class AppDatabase extends RoomDatabase() {
+ *     // BookDao is a class annotated with {@literal @}Dao.
+ *     abstract public BookDao bookDao();
+ *     // UserDao is a class annotated with {@literal @}Dao.
+ *     abstract public UserDao userDao();
+ *     // UserBookDao is a class annotated with {@literal @}Dao.
+ *     abstract public UserBookDao userBookDao();
+ * }
+ * </pre>
+ * The example above defines a class that has 2 tables and 3 DAO classes that are used to access it.
+ * There is no limit on the number of {@link Entity} or {@link Dao} classes but they must be unique
+ * within the Database.
+ * <p>
+ * Instead of running queries on the database directly, you are highly recommended to create
+ * {@link Dao} classes. Using Dao classes will allow you to abstract the database communication in
+ * a more logical layer which will be much easier to mock in tests (compared to running direct
+ * sql queries). It also automatically does the conversion from {@code Cursor} to your application
+ * classes so you don't need to deal with lower level database APIs for most of your data access.
+ * <p>
+ * Room also verifies all of your queries in {@link Dao} classes while the application is being
+ * compiled so that if there is a problem in one of the queries, you will be notified instantly.
+ * @see Dao
+ * @see Entity
+ * @see android.arch.persistence.room.RoomDatabase RoomDatabase
+ */
+@Target(ElementType.TYPE)
+@Retention(RetentionPolicy.CLASS)
+public @interface Database {
+    /**
+     * The list of entities included in the database. Each entity turns into a table in the
+     * database.
+     *
+     * @return The list of entities in the database.
+     */
+    Class[] entities();
+
+    /**
+     * The database version.
+     *
+     * @return The database version.
+     */
+    int version();
+
+    /**
+     * You can set annotation processor argument ({@code room.schemaLocation})
+     * to tell Room to export the schema into a folder. Even though it is not mandatory, it is a
+     * good practice to have version history in your codebase and you should commit that file into
+     * your version control system (but don't ship it with your app!).
+     * <p>
+     * When {@code room.schemaLocation} is set, Room will check this variable and if it is set to
+     * {@code true}, its schema will be exported into the given folder.
+     * <p>
+     * {@code exportSchema} is {@code true} by default but you can disable it for databases when
+     * you don't want to keep history of versions (like an in-memory only database).
+     *
+     * @return Whether the schema should be exported to the given folder when the
+     * {@code room.schemaLocation} argument is set. Defaults to {@code true}.
+     */
+    boolean exportSchema() default true;
+}
diff --git a/room/common/src/main/java/android/arch/persistence/room/Delete.java b/room/common/src/main/java/android/arch/persistence/room/Delete.java
new file mode 100644
index 0000000..678b743
--- /dev/null
+++ b/room/common/src/main/java/android/arch/persistence/room/Delete.java
@@ -0,0 +1,51 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.arch.persistence.room;
+
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+/**
+ * Marks a method in a {@link Dao} annotated class as a delete method.
+ * <p>
+ * The implementation of the method will delete its parameters from the database.
+ * <p>
+ * All of the parameters of the Delete method must either be classes annotated with {@link Entity}
+ * or collections/array of it.
+ * <p>
+ * Example:
+ * <pre>
+ * {@literal @}Dao
+ * public interface MyDao {
+ *     {@literal @}Delete
+ *     public void deleteUsers(User... users);
+ *     {@literal @}Delete
+ *     public void deleteAll(User user1, User user2);
+ *     {@literal @}Delete
+ *     public void deleteWithFriends(User user, List&lt;User&gt; friends);
+ * }
+ * </pre>
+ *
+ * @see Insert
+ * @see Query
+ */
+@Target(ElementType.METHOD)
+@Retention(RetentionPolicy.CLASS)
+public @interface Delete {
+}
diff --git a/room/common/src/main/java/android/arch/persistence/room/Embedded.java b/room/common/src/main/java/android/arch/persistence/room/Embedded.java
new file mode 100644
index 0000000..781f026
--- /dev/null
+++ b/room/common/src/main/java/android/arch/persistence/room/Embedded.java
@@ -0,0 +1,91 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.arch.persistence.room;
+
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+/**
+ * Can be used as an annotation on a field of an {@link Entity} or {@code Pojo} to signal that
+ * nested fields (i.e. fields of the annotated field's class) can be referenced directly in the SQL
+ * queries.
+ * <p>
+ * If the container is an {@link Entity}, these sub fields will be columns in the {@link Entity}'s
+ * database table.
+ * <p>
+ * For example, if you have 2 classes:
+ * <pre>
+ *   public class Coordinates {
+ *       double latitude;
+ *       double longitude;
+ *   }
+ *   public class Address {
+ *       String street;
+ *       {@literal @}Embedded
+ *       Coordinates coordinates;
+ *   }
+ * </pre>
+ * Room will consider {@code latitude} and {@code longitude} as if they are fields of the
+ * {@code Address} class when mapping an SQLite row to {@code Address}.
+ * <p>
+ * So if you have a query that returns {@code street, latitude, longitude}, Room will properly
+ * construct an {@code Address} class.
+ * <p>
+ * If the {@code Address} class is annotated with {@link Entity}, its database table will have 3
+ * columns: {@code street, latitude, longitude}
+ * <p>
+ * If there is a name conflict with the fields of the sub object and the owner object, you can
+ * specify a {@link #prefix()} for the items of the sub object. Note that prefix is always applied
+ * to sub fields even if they have a {@link ColumnInfo} with a specific {@code name}.
+ * <p>
+ * If sub fields of an embedded field has {@link PrimaryKey} annotation, they <b>will not</b> be
+ * considered as primary keys in the owner {@link Entity}.
+ * <p>
+ * When an embedded field is read, if all fields of the embedded field (and its sub fields) are
+ * {@code null} in the {@link android.database.Cursor Cursor}, it is set to {@code null}. Otherwise,
+ * it is constructed.
+ * <p>
+ * Note that even if you have {@link TypeConverter}s that convert a {@code null} column into a
+ * {@code non-null} value, if all columns of the embedded field in the
+ * {@link android.database.Cursor Cursor} are null, the {@link TypeConverter} will never be called
+ * and the embedded field will not be constructed.
+ * <p>
+ * You can override this behavior by annotating the embedded field with
+ * {@link android.support.annotation.NonNull}.
+ */
+@Target(ElementType.FIELD)
+@Retention(RetentionPolicy.CLASS)
+public @interface Embedded {
+    /**
+     * Specifies a prefix to prepend the column names of the fields in the embedded fields.
+     * <p>
+     * For the example above, if we've written:
+     * <pre>
+     *   {@literal @}Embedded(prefix = "foo_")
+     *   Coordinates coordinates;
+     * </pre>
+     * The column names for {@code latitude} and {@code longitude} will be {@code foo_latitude} and
+     * {@code foo_longitude} respectively.
+     * <p>
+     * By default, prefix is the empty string.
+     *
+     * @return The prefix to be used for the fields of the embedded item.
+     */
+    String prefix() default  "";
+}
diff --git a/room/common/src/main/java/android/arch/persistence/room/Entity.java b/room/common/src/main/java/android/arch/persistence/room/Entity.java
new file mode 100644
index 0000000..f54f0f8
--- /dev/null
+++ b/room/common/src/main/java/android/arch/persistence/room/Entity.java
@@ -0,0 +1,120 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.arch.persistence.room;
+
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+/**
+ * Marks a class as an entity. This class will have a mapping SQLite table in the database.
+ * <p>
+ * Each entity must have at least 1 field annotated with {@link PrimaryKey}.
+ * You can also use {@link #primaryKeys()} attribute to define the primary key.
+ * <p>
+ * Each entity must either have a no-arg constructor or a constructor whose parameters match
+ * fields (based on type and name). Constructor does not have to receive all fields as parameters
+ * but if a field is not passed into the constructor, it should either be public or have a public
+ * setter. If a matching constructor is available, Room will always use it. If you don't want it
+ * to use a constructor, you can annotate it with {@link Ignore}.
+ * <p>
+ * When a class is marked as an Entity, all of its fields are persisted. If you would like to
+ * exclude some of its fields, you can mark them with {@link Ignore}.
+ * <p>
+ * Example:
+ * <pre>
+ * {@literal @}Entity
+ * public class User {
+ *   {@literal @}PrimaryKey
+ *   private final int uid;
+ *   private String name;
+ *   {@literal @}ColumnInfo(name = "last_name")
+ *   private String lastName;
+ *
+ *   public User(int uid) {
+ *       this.uid = uid;
+ *   }
+ *   public String getLastName() {
+ *       return lastName;
+ *   }
+ *   public void setLastName(String lastName) {
+ *       this.lastName = lastName;
+ *   }
+ * }
+ * </pre>
+ *
+ * @see Dao
+ * @see Database
+ * @see PrimaryKey
+ * @see ColumnInfo
+ * @see Index
+ */
+@Target(ElementType.TYPE)
+@Retention(RetentionPolicy.CLASS)
+public @interface Entity {
+    /**
+     * The table name in the SQLite database. If not set, defaults to the class name.
+     *
+     * @return The SQLite tableName of the Entity.
+     */
+    String tableName() default "";
+
+    /**
+     * List of indices on the table.
+     *
+     * @return The list of indices on the table.
+     */
+    Index[] indices() default {};
+
+    /**
+     * If set to {@code true}, any Index defined in parent classes of this class will be carried
+     * over to the current {@code Entity}. Note that if you set this to {@code true}, even if the
+     * {@code Entity} has a parent which sets this value to {@code false}, the {@code Entity} will
+     * still inherit indices from it and its parents.
+     * <p>
+     * When the {@code Entity} inherits an index from the parent, it is <b>always</b> renamed with
+     * the default naming schema since SQLite <b>does not</b> allow using the same index name in
+     * multiple tables. See {@link Index} for the details of the default name.
+     * <p>
+     * By default, indices defined in parent classes are dropped to avoid unexpected indices.
+     * When this happens, you will receive a {@link RoomWarnings#INDEX_FROM_PARENT_FIELD_IS_DROPPED}
+     * or {@link RoomWarnings#INDEX_FROM_PARENT_IS_DROPPED} warning during compilation.
+     *
+     * @return True if indices from parent classes should be automatically inherited by this Entity,
+     *         false otherwise. Defaults to false.
+     */
+    boolean inheritSuperIndices() default false;
+
+    /**
+     * The list of Primary Key column names.
+     * <p>
+     * If you would like to define an auto generated primary key, you can use {@link PrimaryKey}
+     * annotation on the field with {@link PrimaryKey#autoGenerate()} set to {@code true}.
+     *
+     * @return The primary key of this Entity. Can be empty if the class has a field annotated
+     * with {@link PrimaryKey}.
+     */
+    String[] primaryKeys() default {};
+
+    /**
+     * List of {@link ForeignKey} constraints on this entity.
+     *
+     * @return The list of {@link ForeignKey} constraints on this entity.
+     */
+    ForeignKey[] foreignKeys() default {};
+}
diff --git a/room/common/src/main/java/android/arch/persistence/room/ForeignKey.java b/room/common/src/main/java/android/arch/persistence/room/ForeignKey.java
new file mode 100644
index 0000000..4ba0fb3
--- /dev/null
+++ b/room/common/src/main/java/android/arch/persistence/room/ForeignKey.java
@@ -0,0 +1,166 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.arch.persistence.room;
+
+import android.support.annotation.IntDef;
+
+/**
+ * Declares a foreign key on another {@link Entity}.
+ * <p>
+ * Foreign keys allows you to specify constraints across Entities such that SQLite will ensure that
+ * the relationship is valid when you modify the database.
+ * <p>
+ * When a foreign key constraint is specified, SQLite requires the referenced columns to be part of
+ * a unique index in the parent table or the primary key of that table. You must create a unique
+ * index in the parent entity that covers the referenced columns (Room will verify this at compile
+ * time and print an error if it is missing).
+ * <p>
+ * It is also recommended to create an index on the child table to avoid full table scans when the
+ * parent table is modified. If a suitable index on the child table is missing, Room will print
+ * {@link RoomWarnings#MISSING_INDEX_ON_FOREIGN_KEY_CHILD} warning.
+ * <p>
+ * A foreign key constraint can be deferred until the transaction is complete. This is useful if
+ * you are doing bulk inserts into the database in a single transaction. By default, foreign key
+ * constraints are immediate but you can change this value by setting {@link #deferred()} to
+ * {@code true}. You can also use
+ * <a href="https://sqlite.org/pragma.html#pragma_defer_foreign_keys">defer_foreign_keys</a> PRAGMA
+ * to defer them depending on your transaction.
+ * <p>
+ * Please refer to the SQLite <a href="https://sqlite.org/foreignkeys.html>foreign keys</a>
+ * documentation for details.
+ */
+public @interface ForeignKey {
+    /**
+     * The parent Entity to reference. It must be a class annotated with {@link Entity} and
+     * referenced in the same database.
+     *
+     * @return The parent Entity.
+     */
+    Class entity();
+
+    /**
+     * The list of column names in the parent {@link Entity}.
+     * <p>
+     * Number of columns must match the number of columns specified in {@link #childColumns()}.
+     *
+     * @return The list of column names in the parent Entity.
+     * @see #childColumns()
+     */
+    String[] parentColumns();
+
+    /**
+     * The list of column names in the current {@link Entity}.
+     * <p>
+     * Number of columns must match the number of columns specified in {@link #parentColumns()}.
+     *
+     * @return The list of column names in the current Entity.
+     */
+    String[] childColumns();
+
+    /**
+     * Action to take when the parent {@link Entity} is deleted from the database.
+     * <p>
+     * By default, {@link #NO_ACTION} is used.
+     *
+     * @return The action to take when the referenced entity is deleted from the database.
+     */
+    @Action int onDelete() default NO_ACTION;
+
+    /**
+     * Action to take when the parent {@link Entity} is updated in the database.
+     * <p>
+     * By default, {@link #NO_ACTION} is used.
+     *
+     * @return The action to take when the referenced entity is updated in the database.
+     */
+    @Action int onUpdate() default NO_ACTION;
+
+    /**
+     * * A foreign key constraint can be deferred until the transaction is complete. This is useful
+     * if you are doing bulk inserts into the database in a single transaction. By default, foreign
+     * key constraints are immediate but you can change it by setting this field to {@code true}.
+     * You can also use
+     * <a href="https://sqlite.org/pragma.html#pragma_defer_foreign_keys">defer_foreign_keys</a>
+     * PRAGMA to defer them depending on your transaction.
+     *
+     * @return Whether the foreign key constraint should be deferred until the transaction is
+     * complete. Defaults to {@code false}.
+     */
+    boolean deferred() default false;
+
+    /**
+     * Possible value for {@link #onDelete()} or {@link #onUpdate()}.
+     * <p>
+     * When a parent key is modified or deleted from the database, no special action is taken.
+     * This means that SQLite will not make any effort to fix the constraint failure, instead,
+     * reject the change.
+     */
+    int NO_ACTION = 1;
+
+    /**
+     * Possible value for {@link #onDelete()} or {@link #onUpdate()}.
+     * <p>
+     * The RESTRICT action means that the application is prohibited from deleting
+     * (for {@link #onDelete()}) or modifying (for {@link #onUpdate()}) a parent key when there
+     * exists one or more child keys mapped to it. The difference between the effect of a RESTRICT
+     * action and normal foreign key constraint enforcement is that the RESTRICT action processing
+     * happens as soon as the field is updated - not at the end of the current statement as it would
+     * with an immediate constraint, or at the end of the current transaction as it would with a
+     * {@link #deferred()} constraint.
+     * <p>
+     * Even if the foreign key constraint it is attached to is {@link #deferred()}, configuring a
+     * RESTRICT action causes SQLite to return an error immediately if a parent key with dependent
+     * child keys is deleted or modified.
+     */
+    int RESTRICT = 2;
+
+    /**
+     * Possible value for {@link #onDelete()} or {@link #onUpdate()}.
+     * <p>
+     * If the configured action is "SET NULL", then when a parent key is deleted
+     * (for {@link #onDelete()}) or modified (for {@link #onUpdate()}), the child key columns of all
+     * rows in the child table that mapped to the parent key are set to contain {@code NULL} values.
+     */
+    int SET_NULL = 3;
+
+    /**
+     * Possible value for {@link #onDelete()} or {@link #onUpdate()}.
+     * <p>
+     * The "SET DEFAULT" actions are similar to {@link #SET_NULL}, except that each of the child key
+     * columns is set to contain the columns default value instead of {@code NULL}.
+     */
+    int SET_DEFAULT = 4;
+
+    /**
+     * Possible value for {@link #onDelete()} or {@link #onUpdate()}.
+     * <p>
+     * A "CASCADE" action propagates the delete or update operation on the parent key to each
+     * dependent child key. For {@link #onDelete()} action, this means that each row in the child
+     * entity that was associated with the deleted parent row is also deleted. For an
+     * {@link #onUpdate()} action, it means that the values stored in each dependent child key are
+     * modified to match the new parent key values.
+     */
+    int CASCADE = 5;
+
+    /**
+     * Constants definition for values that can be used in {@link #onDelete()} and
+     * {@link #onUpdate()}.
+     */
+    @IntDef({NO_ACTION, RESTRICT, SET_NULL, SET_DEFAULT, CASCADE})
+    @interface Action {
+    }
+}
diff --git a/room/common/src/main/java/android/arch/persistence/room/Ignore.java b/room/common/src/main/java/android/arch/persistence/room/Ignore.java
new file mode 100644
index 0000000..6effc33
--- /dev/null
+++ b/room/common/src/main/java/android/arch/persistence/room/Ignore.java
@@ -0,0 +1,33 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.arch.persistence.room;
+
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+/**
+ * Ignores the marked element from Room's processing logic.
+ * <p>
+ * This annotation can be used in multiple places where Room processor runs. For instance, you can
+ * add it to a field of an {@link Entity} and Room will not persist that field.
+ */
+@Target({ElementType.METHOD, ElementType.FIELD, ElementType.CONSTRUCTOR})
+@Retention(RetentionPolicy.CLASS)
+public @interface Ignore {
+}
diff --git a/room/common/src/main/java/android/arch/persistence/room/Index.java b/room/common/src/main/java/android/arch/persistence/room/Index.java
new file mode 100644
index 0000000..b814efd
--- /dev/null
+++ b/room/common/src/main/java/android/arch/persistence/room/Index.java
@@ -0,0 +1,74 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.arch.persistence.room;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+/**
+ * Declares an index on an Entity.
+ * see: <a href="https://sqlite.org/lang_createindex.html">SQLite Index Documentation</a>
+ * <p>
+ * Adding an index usually speeds up your select queries but will slow down other queries like
+ * insert or update. You should be careful when adding indices to ensure that this additional cost
+ * is worth the gain.
+ * <p>
+ * There are 2 ways to define an index in an {@link Entity}. You can either set
+ * {@link ColumnInfo#index()} property to index individual fields or define composite indices via
+ * {@link Entity#indices()}.
+ * <p>
+ * If an indexed field is embedded into another Entity via {@link Embedded}, it is <b>NOT</b>
+ * added as an index to the containing {@link Entity}. If you want to keep it indexed, you must
+ * re-declare it in the containing {@link Entity}.
+ * <p>
+ * Similarly, if an {@link Entity} extends another class, indices from the super classes are
+ * <b>NOT</b> inherited. You must re-declare them in the child {@link Entity} or set
+ * {@link Entity#inheritSuperIndices()} to {@code true}.
+ * */
+@Target({})
+@Retention(RetentionPolicy.CLASS)
+public @interface Index {
+    /**
+     * List of column names in the Index.
+     * <p>
+     * The order of columns is important as it defines when SQLite can use a particular index.
+     * See <a href="https://www.sqlite.org/optoverview.html">SQLite documentation</a> for details on
+     * index usage in the query optimizer.
+     *
+     * @return The list of column names in the Index.
+     */
+    String[] value();
+
+    /**
+     * Name of the index. If not set, Room will set it to the list of columns joined by '_' and
+     * prefixed by "index_${tableName}". So if you have a table with name "Foo" and with an index
+     * of {"bar", "baz"}, generated index name will be  "index_Foo_bar_baz". If you need to specify
+     * the index in a query, you should never rely on this name, instead, specify a name for your
+     * index.
+     *
+     * @return The name of the index.
+     */
+    String name() default "";
+
+    /**
+     * If set to true, this will be a unique index and any duplicates will be rejected.
+     *
+     * @return True if index is unique. False by default.
+     */
+    boolean unique() default false;
+}
diff --git a/room/common/src/main/java/android/arch/persistence/room/Insert.java b/room/common/src/main/java/android/arch/persistence/room/Insert.java
new file mode 100644
index 0000000..802dd96
--- /dev/null
+++ b/room/common/src/main/java/android/arch/persistence/room/Insert.java
@@ -0,0 +1,59 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.arch.persistence.room;
+
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+/**
+ * Marks a method in a {@link Dao} annotated class as an insert method.
+ * <p>
+ * The implementation of the method will insert its parameters into the database.
+ * <p>
+ * All of the parameters of the Insert method must either be classes annotated with {@link Entity}
+ * or collections/array of it.
+ * <p>
+ * Example:
+ * <pre>
+ * {@literal @}Dao
+ * public interface MyDao {
+ *     {@literal @}Insert(onConflict = OnConflictStrategy.REPLACE)
+ *     public void insertUsers(User... users);
+ *     {@literal @}Insert
+ *     public void insertBoth(User user1, User user2);
+ *     {@literal @}Insert
+ *     public void insertWithFriends(User user, List&lt;User&gt; friends);
+ * }
+ * </pre>
+ *
+ * @see Update
+ * @see Delete
+ */
+@Target({ElementType.METHOD})
+@Retention(RetentionPolicy.CLASS)
+public @interface Insert {
+    /**
+     * What to do if a conflict happens.
+     * @see <a href="https://sqlite.org/lang_conflict.html">SQLite conflict documentation</a>
+     *
+     * @return How to handle conflicts. Defaults to {@link OnConflictStrategy#ABORT}.
+     */
+    @OnConflictStrategy
+    int onConflict() default OnConflictStrategy.ABORT;
+}
diff --git a/room/common/src/main/java/android/arch/persistence/room/OnConflictStrategy.java b/room/common/src/main/java/android/arch/persistence/room/OnConflictStrategy.java
new file mode 100644
index 0000000..5217b61
--- /dev/null
+++ b/room/common/src/main/java/android/arch/persistence/room/OnConflictStrategy.java
@@ -0,0 +1,56 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.arch.persistence.room;
+
+import static java.lang.annotation.RetentionPolicy.SOURCE;
+
+import android.support.annotation.IntDef;
+
+import java.lang.annotation.Retention;
+
+/**
+ * Set of conflict handling strategies for various {@link Dao} methods.
+ * <p>
+ * Check <a href="https://sqlite.org/lang_conflict.html">SQLite conflict documentation</a> for
+ * details.
+ */
+@Retention(SOURCE)
+@IntDef({OnConflictStrategy.REPLACE, OnConflictStrategy.ROLLBACK, OnConflictStrategy.ABORT,
+        OnConflictStrategy.FAIL, OnConflictStrategy.IGNORE})
+public @interface OnConflictStrategy {
+    /**
+     * OnConflict strategy constant to replace the old data and continue the transaction.
+     */
+    int REPLACE = 1;
+    /**
+     * OnConflict strategy constant to rollback the transaction.
+     */
+    int ROLLBACK = 2;
+    /**
+     * OnConflict strategy constant to abort the transaction.
+     */
+    int ABORT = 3;
+    /**
+     * OnConflict strategy constant to fail the transaction.
+     */
+    int FAIL = 4;
+    /**
+     * OnConflict strategy constant to ignore the conflict.
+     */
+    int IGNORE = 5;
+
+}
diff --git a/room/common/src/main/java/android/arch/persistence/room/PrimaryKey.java b/room/common/src/main/java/android/arch/persistence/room/PrimaryKey.java
new file mode 100644
index 0000000..9a8062ca
--- /dev/null
+++ b/room/common/src/main/java/android/arch/persistence/room/PrimaryKey.java
@@ -0,0 +1,58 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.arch.persistence.room;
+
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+/**
+ * Marks a field in an {@link Entity} as the primary key.
+ * <p>
+ * If you would like to define a composite primary key, you should use {@link Entity#primaryKeys()}
+ * method.
+ * <p>
+ * Each {@link Entity} must declare a primary key unless one of its super classes declares a
+ * primary key. If both an {@link Entity} and its super class defines a {@code PrimaryKey}, the
+ * child's {@code PrimaryKey} definition will override the parent's {@code PrimaryKey}.
+ * <p>
+ * If {@code PrimaryKey} annotation is used on a {@link Embedded}d field, all columns inherited
+ * from that embedded field becomes the composite primary key (including its grand children
+ * fields).
+ */
+@Target(ElementType.FIELD)
+@Retention(RetentionPolicy.CLASS)
+public @interface PrimaryKey {
+    /**
+     * Set to true to let SQLite generate the unique id.
+     * <p>
+     * When set to {@code true}, the SQLite type affinity for the field should be {@code INTEGER}.
+     * <p>
+     * If the field type is {@code long} or {@code int} (or its TypeConverter converts it to a
+     * {@code long} or {@code int}), {@link Insert} methods treat {@code 0} as not-set while
+     * inserting the item.
+     * <p>
+     * If the field's type is {@link Integer} or {@link Long} (or its TypeConverter converts it to
+     * an {@link Integer} or a {@link Long}), {@link Insert} methods treat {@code null} as
+     * not-set while inserting the item.
+     *
+     * @return Whether the primary key should be auto-generated by SQLite or not. Defaults
+     * to false.
+     */
+    boolean autoGenerate() default false;
+}
diff --git a/room/common/src/main/java/android/arch/persistence/room/Query.java b/room/common/src/main/java/android/arch/persistence/room/Query.java
new file mode 100644
index 0000000..4462547
--- /dev/null
+++ b/room/common/src/main/java/android/arch/persistence/room/Query.java
@@ -0,0 +1,102 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.arch.persistence.room;
+
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+/**
+ * Marks a method in a {@link Dao} annotated class as a query method.
+ * <p>
+ * The value of the annotation includes the query that will be run when this method is called. This
+ * query is <b>verified at compile time</b> by Room to ensure that it compiles fine against the
+ * database.
+ * <p>
+ * The arguments of the method will be bound to the bind arguments in the SQL statement. See
+ * <href="https://www.sqlite.org/c3ref/bind_blob.html">SQLite's binding documentation</> for
+ * details of bind arguments in SQLite.
+ * <p>
+ * Room only supports named bind parameter {@code :name} to avoid any confusion between the
+ * method parameters and the query bind parameters.
+ * <p>
+ * Room will automatically bind the parameters of the method into the bind arguments. This is done
+ * by matching the name of the parameters to the name of the bind arguments.
+ * <pre>
+ *     {@literal @}Query("SELECT * FROM user WHERE user_name LIKE :name AND last_name LIKE :last")
+ *     public abstract List&lt;User&gt; findUsersByNameAndLastName(String name, String last);
+ * </pre>
+ * <p>
+ * As an extension over SQLite bind arguments, Room supports binding a list of parameters to the
+ * query. At runtime, Room will build the correct query to have matching number of bind arguments
+ * depending on the number of items in the method parameter.
+ * <pre>
+ *     {@literal @}Query("SELECT * FROM user WHERE uid IN(:userIds)")
+ *     public abstract List<User> findByIds(int[] userIds);
+ * </pre>
+ * For the example above, if the {@code userIds} is an array of 3 elements, Room will run the
+ * query as: {@code SELECT * FROM user WHERE uid IN(?, ?, ?)} and bind each item in the
+ * {@code userIds} array into the statement.
+ * <p>
+ * There are 3 types of queries supported in {@code Query} methods: SELECT, UPDATE and DELETE.
+ * <p>
+ * For SELECT queries, Room will infer the result contents from the method's return type and
+ * generate the code that will automatically convert the query result into the method's return
+ * type. For single result queries, the return type can be any java object. For queries that return
+ * multiple values, you can use {@link java.util.List} or {@code Array}. In addition to these, any
+ * query may return {@link android.database.Cursor Cursor} or any query result can be wrapped in
+ * a {@link android.arch.lifecycle.LiveData LiveData}.
+ * <p>
+ * <b>RxJava2</b> If you are using RxJava2, you can also return {@code Flowable<T>} or
+ * {@code Publisher<T>} from query methods. Since Reactive Streams does not allow {@code null}, if
+ * the query returns a nullable type, it will not dispatch anything if the value is {@code null}
+ * (like fetching an {@link Entity} row that does not exist).
+ * You can return {@code Flowable<T[]>} or {@code Flowable<List<T>>} to workaround this limitation.
+ * <p>
+ * UPDATE or DELETE queries can return {@code void} or {@code int}. If it is an {@code int},
+ * the value is the number of rows affected by this query.
+ * <p>
+ * You can return arbitrary POJOs from your query methods as long as the fields of the POJO match
+ * the column names in the query result.
+ * For example, if you have class:
+ * <pre>
+ * class UserName {
+ *     public String name;
+ *     {@literal @}ColumnInfo(name = "last_name")
+ *     public String lastName;
+ * }
+ * </pre>
+ * You can write a query like this:
+ * <pre>
+ *     {@literal @}Query("SELECT last_name, name FROM user WHERE uid = :userId LIMIT 1")
+ *     public abstract UserName findOneUserName(int userId);
+ * </pre>
+ * And Room will create the correct implementation to convert the query result into a
+ * {@code UserName} object. If there is a mismatch between the query result and the fields of the
+ * POJO, as long as there is at least 1 field match, Room prints a
+ * {@link RoomWarnings#CURSOR_MISMATCH} warning and sets as many fields as it can.
+ */
+@Target(ElementType.METHOD)
+@Retention(RetentionPolicy.CLASS)
+public @interface Query {
+    /**
+     * The SQLite query to be run.
+     * @return The query to be run.
+     */
+    String value();
+}
diff --git a/room/common/src/main/java/android/arch/persistence/room/Relation.java b/room/common/src/main/java/android/arch/persistence/room/Relation.java
new file mode 100644
index 0000000..5cb4bf7
--- /dev/null
+++ b/room/common/src/main/java/android/arch/persistence/room/Relation.java
@@ -0,0 +1,130 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.arch.persistence.room;
+
+/**
+ * A convenience annotation which can be used in a Pojo to automatically fetch relation entities.
+ * When the Pojo is returned from a query, all of its relations are also fetched by Room.
+ *
+ * <pre>
+ * {@literal @}Entity
+ * public class Pet {
+ *     int userId;
+ *     String name;
+ *     // other fields
+ * }
+ * public class UserNameAndAllPets {
+ *   public int id;
+ *   public String name;
+ *   {@literal @}Relation(parentColumn = "id", entityColumn = "userId")
+ *   public List&lt;Pet&gt; pets;
+ * }
+ *
+ * {@literal @}Dao
+ * public interface UserPetDao {
+ *     {@literal @}Query("SELECT id, name from User WHERE age &gte; ?")
+ *     public List&lt;UserNameAndAllPets&gt; loadUserAndPets(int minAge);
+ * }
+ * </pre>
+ * <p>
+ * The type of the field annotated with {@code Relation} must be a {@link java.util.List} or
+ * {@link java.util.Set}. By default, the {@link Entity} type is inferred from the return type.
+ * If you would like to return a different object, you can specify the {@link #entity()} property
+ * in the annotation.
+ * <pre>
+ * public class User {
+ *     int id;
+ *     // other fields
+ * }
+ * public class PetNameAndId {
+ *     int id;
+ *     String name;
+ * }
+ * public class UserAllPets {
+ *   {@literal @}Embedded
+ *   public User user;
+ *   {@literal @}Relation(parentColumn = "user.id", entityColumn = "userId", entity = Pet.class)
+ *   public List<PetNameAndId> pets;
+ * }
+ * {@literal @}Dao
+ * public interface UserPetDao {
+ *     {@literal @}Query("SELECT * from User WHERE age &gte; ?")
+ *     public List&lt;UserAllPets&gt; loadUserAndPets(int minAge);
+ * }
+ * </pre>
+ * <p>
+ * In the example above, {@code PetNameAndId} is a regular but all of fields are fetched
+ * from the {@code entity} defined in the {@code @Relation} annotation (<i>Pet</i>).
+ * {@code PetNameAndId} could also define its own relations all of which would also be fetched
+ * automatically.
+ * <p>
+ * If you would like to specify which columns are fetched from the child {@link Entity}, you can
+ * use {@link #projection()} property in the {@code Relation} annotation.
+ * <pre>
+ * public class UserAndAllPets {
+ *   {@literal @}Embedded
+ *   public User user;
+ *   {@literal @}Relation(parentColumn = "user.id", entityColumn = "userId", entity = Pet.class,
+ *           projection = {"name"})
+ *   public List<String> petNames;
+ * }
+ * </pre>
+ * <p>
+ * Note that {@code @Relation} annotation can be used only in Pojo classes, an {@link Entity} class
+ * cannot have relations. This is a design decision to avoid common pitfalls in {@link Entity}
+ * setups. You can read more about it in the main Room documentation. When loading data, you can
+ * simply work around this limitation by creating Pojo classes that extend the {@link Entity}.
+ */
+public @interface Relation {
+    /**
+     * The entity to fetch the item from. You don't need to set this if the entity matches the
+     * type argument in the return type.
+     *
+     * @return The entity to fetch from. By default, inherited from the return type.
+     */
+    Class entity() default Object.class;
+
+    /**
+     * Reference field in the parent Pojo.
+     * <p>
+     * If you would like to access to a sub item of a {@link Embedded}d field, you can use
+     * the {@code .} notation.
+     * <p>
+     * For instance, if you have a {@link Embedded}d field named {@code user} with a sub field
+     * {@code id}, you can reference it via {@code user.id}.
+     * <p>
+     * This value will be matched against the value defined in {@link #entityColumn()}.
+     *
+     * @return The field reference in the parent object.
+     */
+    String parentColumn();
+
+    /**
+     * The field path to match in the {@link #entity()}. This value will be matched against the
+     * value defined in {@link #parentColumn()}.
+     */
+    String entityColumn();
+
+    /**
+     * If sub fields should be fetched from the entity, you can specify them using this field.
+     * <p>
+     * By default, inferred from the the return type.
+     *
+     * @return The list of columns to be selected from the {@link #entity()}.
+     */
+    String[] projection() default {};
+}
diff --git a/room/common/src/main/java/android/arch/persistence/room/RoomMasterTable.java b/room/common/src/main/java/android/arch/persistence/room/RoomMasterTable.java
new file mode 100644
index 0000000..621845d
--- /dev/null
+++ b/room/common/src/main/java/android/arch/persistence/room/RoomMasterTable.java
@@ -0,0 +1,55 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.arch.persistence.room;
+
+import android.support.annotation.RestrictTo;
+
+/**
+ * Schema information about Room's master table.
+ *
+ * @hide
+ */
+@SuppressWarnings("WeakerAccess")
+@RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
+public class RoomMasterTable {
+    /**
+     * The master table where room keeps its metadata information.
+     */
+    public static final String TABLE_NAME = "room_master_table";
+    // must match the runtime property Room#MASTER_TABLE_NAME
+    public static final String NAME = "room_master_table";
+    private static final String COLUMN_ID = "id";
+    private static final String COLUMN_IDENTITY_HASH = "identity_hash";
+    public static final String DEFAULT_ID = "42";
+
+    public static final String CREATE_QUERY = "CREATE TABLE IF NOT EXISTS " + TABLE_NAME + " ("
+            + COLUMN_ID + " INTEGER PRIMARY KEY,"
+            + COLUMN_IDENTITY_HASH + " TEXT)";
+
+    public static final String READ_QUERY = "SELECT " + COLUMN_IDENTITY_HASH
+            + " FROM " + TABLE_NAME + " WHERE "
+            + COLUMN_ID + " = " + DEFAULT_ID + " LIMIT 1";
+
+    /**
+     * We don't escape here since we know what we are passing.
+     */
+    public static String createInsertQuery(String hash) {
+        return "INSERT OR REPLACE INTO " + TABLE_NAME + " ("
+                + COLUMN_ID + "," + COLUMN_IDENTITY_HASH + ")"
+                + " VALUES(" + DEFAULT_ID + ", \"" + hash + "\")";
+    }
+}
diff --git a/room/common/src/main/java/android/arch/persistence/room/RoomWarnings.java b/room/common/src/main/java/android/arch/persistence/room/RoomWarnings.java
new file mode 100644
index 0000000..91f32e4
--- /dev/null
+++ b/room/common/src/main/java/android/arch/persistence/room/RoomWarnings.java
@@ -0,0 +1,120 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.arch.persistence.room;
+
+/**
+ * The list of warnings that are produced by Room.
+ * <p>
+ * You can use these values inside a {@link SuppressWarnings} annotation to disable the warnings.
+ */
+@SuppressWarnings({"unused", "WeakerAccess"})
+public class RoomWarnings {
+    /**
+     * The warning dispatched by Room when the return value of a {@link Query} method does not
+     * exactly match the fields in the query result.
+     */
+    // if you change this, don't forget to change android.arch.persistence.room.vo.Warning
+    public static final String CURSOR_MISMATCH = "ROOM_CURSOR_MISMATCH";
+
+    /**
+     * Reported when Room cannot verify database queries during compilation due to lack of
+     * tmp dir access in JVM.
+     */
+    public static final String MISSING_JAVA_TMP_DIR = "ROOM_MISSING_JAVA_TMP_DIR";
+
+    /**
+     * Reported when Room cannot verify database queries during compilation. This usually happens
+     * when it cannot find the SQLite JDBC driver on the host machine.
+     * <p>
+     * Room can function without query verification but its functionality will be limited.
+     */
+    public static final String CANNOT_CREATE_VERIFICATION_DATABASE =
+            "ROOM_CANNOT_CREATE_VERIFICATION_DATABASE";
+
+    /**
+     * Reported when an {@link Entity} field that is annotated with {@link Embedded} has a
+     * sub field which is annotated with {@link PrimaryKey} but the {@link PrimaryKey} is dropped
+     * while composing it into the parent object.
+     */
+    public static final String PRIMARY_KEY_FROM_EMBEDDED_IS_DROPPED =
+            "ROOM_EMBEDDED_PRIMARY_KEY_IS_DROPPED";
+
+    /**
+     * Reported when an {@link Entity} field that is annotated with {@link Embedded} has a
+     * sub field which has a {@link ColumnInfo} annotation with {@code index = true}.
+     * <p>
+     * You can re-define the index in the containing {@link Entity}.
+     */
+    public static final String INDEX_FROM_EMBEDDED_FIELD_IS_DROPPED =
+            "ROOM_EMBEDDED_INDEX_IS_DROPPED";
+
+    /**
+     * Reported when an {@link Entity} that has a {@link Embedded}d field whose type is another
+     * {@link Entity} and that {@link Entity} has some indices defined.
+     * These indices will NOT be created in the containing {@link Entity}. If you want to preserve
+     * them, you can re-define them in the containing {@link Entity}.
+     */
+    public static final String INDEX_FROM_EMBEDDED_ENTITY_IS_DROPPED =
+            "ROOM_EMBEDDED_ENTITY_INDEX_IS_DROPPED";
+
+    /**
+     * Reported when an {@link Entity}'s parent declares an {@link Index}. Room does not
+     * automatically inherit these indices to avoid hidden costs or unexpected constraints.
+     * <p>
+     * If you want your child class to have the indices of the parent, you must re-declare
+     * them in the child class. Alternatively, you can set {@link Entity#inheritSuperIndices()}
+     * to {@code true}.
+     */
+    public static final String INDEX_FROM_PARENT_IS_DROPPED =
+            "ROOM_PARENT_INDEX_IS_DROPPED";
+
+    /**
+     * Reported when an {@link Entity} inherits a field from its super class and the field has a
+     * {@link ColumnInfo} annotation with {@code index = true}.
+     * <p>
+     * These indices are dropped for the {@link Entity} and you would need to re-declare them if
+     * you want to keep them. Alternatively, you can set {@link Entity#inheritSuperIndices()}
+     * to {@code true}.
+     */
+    public static final String INDEX_FROM_PARENT_FIELD_IS_DROPPED =
+            "ROOM_PARENT_FIELD_INDEX_IS_DROPPED";
+
+    /**
+     * Reported when a {@link Relation} {@link Entity}'s SQLite column type does not match the type
+     * in the parent. Room will still do the matching using {@code String} representations.
+     */
+    public static final String RELATION_TYPE_MISMATCH = "ROOM_RELATION_TYPE_MISMATCH";
+
+    /**
+     * Reported when a `room.schemaLocation` argument is not provided into the annotation processor.
+     * You can either set {@link Database#exportSchema()} to {@code false} or provide
+     * `room.schemaLocation` to the annotation processor. You are strongly adviced to provide it
+     * and also commit them into your version control system.
+     */
+    public static final String MISSING_SCHEMA_LOCATION = "ROOM_MISSING_SCHEMA_LOCATION";
+
+    /**
+     * When there is a foreign key from Entity A to Entity B, it is a good idea to index the
+     * reference columns in B, otherwise, each modification on Entity A will trigger a full table
+     * scan on Entity B.
+     * <p>
+     * If Room cannot find a proper index in the child entity (Entity B in this case), Room will
+     * print this warning.
+     */
+    public static final String MISSING_INDEX_ON_FOREIGN_KEY_CHILD =
+            "ROOM_MISSING_FOREIGN_KEY_CHILD_INDEX";
+}
diff --git a/room/common/src/main/java/android/arch/persistence/room/SkipQueryVerification.java b/room/common/src/main/java/android/arch/persistence/room/SkipQueryVerification.java
new file mode 100644
index 0000000..dc2ded3
--- /dev/null
+++ b/room/common/src/main/java/android/arch/persistence/room/SkipQueryVerification.java
@@ -0,0 +1,42 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.arch.persistence.room;
+
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+/**
+ * Skips database verification for the annotated element.
+ * <p>
+ * If it is a class annotated with {@link Database}, none of the queries for the database will
+ * be verified at compile time.
+ * <p>
+ * If it is a class annotated with {@link Dao}, none of the queries in the Dao class will
+ * be verified at compile time.
+ * <p>
+ * If it is a method in a Dao class, just the method's sql verification will be skipped.
+ * <p>
+ * You should use this as the last resort if Room cannot properly understand your query and you are
+ * 100% sure it works. Removing validation may limit the functionality of Room since it won't be
+ * able to understand the query response.
+ */
+@Target({ElementType.METHOD, ElementType.TYPE})
+@Retention(RetentionPolicy.CLASS)
+public @interface SkipQueryVerification {
+}
diff --git a/room/common/src/main/java/android/arch/persistence/room/TypeConverter.java b/room/common/src/main/java/android/arch/persistence/room/TypeConverter.java
new file mode 100644
index 0000000..bea68c8
--- /dev/null
+++ b/room/common/src/main/java/android/arch/persistence/room/TypeConverter.java
@@ -0,0 +1,52 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.arch.persistence.room;
+
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+/**
+ * Marks a method as a type converter. A class can have as many @Converter methods as it needs.
+ * <p>
+ * Each converter method should receive 1 parameter and have non-void return type.
+ *
+ * <pre>
+ * // example converter for java.util.Date
+ * public static class Converters {
+ *    {@literal @}TypeConverter
+ *    public Date fromTimestamp(Long value) {
+ *        return value == null ? null : new Date(value);
+ *    }
+ *
+ *    {@literal @}TypeConverter
+ *    public Long dateToTimestamp(Date date) {
+ *        if (date == null) {
+ *            return null;
+ *        } else {
+ *            return date.getTime();
+ *        }
+ *    }
+ *}
+ * </pre>
+ * @see TypeConverters
+ */
+@Target({ElementType.METHOD})
+@Retention(RetentionPolicy.CLASS)
+public @interface TypeConverter {
+}
diff --git a/room/common/src/main/java/android/arch/persistence/room/TypeConverters.java b/room/common/src/main/java/android/arch/persistence/room/TypeConverters.java
new file mode 100644
index 0000000..7f7e6ab
--- /dev/null
+++ b/room/common/src/main/java/android/arch/persistence/room/TypeConverters.java
@@ -0,0 +1,50 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.arch.persistence.room;
+
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+/**
+ * Specifies additional type converters that Room can use. The TypeConverter is added to the scope
+ * of the element so if you put it on a class / interface, all methods / fields in that class will
+ * be able to use the converters.
+ * <ul>
+ * <li>If you put it on a {@link Database}, all Daos and Entities in that database will be able to
+ * use it.
+ * <li>If you put it on a {@link Dao}, all methods in the Dao will be able to use it.
+ * <li>If you put it on an {@link Entity}, all fields of the Entity will be able to use it.
+ * <li>If you put it on a POJO, all fields of the POJO will be able to use it.
+ * <li>If you put it on an {@link Entity} field, only that field will be able to use it.
+ * <li>If you put it on a {@link Dao} method, all parameters of the method will be able to use it.
+ * <li>If you put it on a {@link Dao} method parameter, just that field will be able to use it.
+ * </ul>
+ * @see TypeConverter
+ */
+@Target({ElementType.METHOD, ElementType.PARAMETER, ElementType.TYPE, ElementType.FIELD})
+@Retention(RetentionPolicy.CLASS)
+public @interface TypeConverters {
+    /**
+     * The list of type converter classes. If converter methods are not static, Room will create
+     * an instance of these classes.
+     *
+     * @return The list of classes that contains the converter methods.
+     */
+    Class<?>[] value();
+}
diff --git a/room/common/src/main/java/android/arch/persistence/room/Update.java b/room/common/src/main/java/android/arch/persistence/room/Update.java
new file mode 100644
index 0000000..670a085
--- /dev/null
+++ b/room/common/src/main/java/android/arch/persistence/room/Update.java
@@ -0,0 +1,40 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package android.arch.persistence.room;
+
+/**
+ * Marks a method in a {@link Dao} annotated class as an update method.
+ * <p>
+ * The implementation of the method will update its parameters in the database if they already
+ * exists (checked by primary keys). If they don't already exists, this option will not change the
+ * database.
+ * <p>
+ * All of the parameters of the Update method must either be classes annotated with {@link Entity}
+ * or collections/array of it.
+ *
+ * @see Insert
+ * @see Delete
+ */
+public @interface Update {
+    /**
+     * What to do if a conflict happens.
+     * @see <a href="https://sqlite.org/lang_conflict.html">SQLite conflict documentation</a>
+     *
+     * @return How to handle conflicts. Defaults to {@link OnConflictStrategy#ABORT}.
+     */
+    @OnConflictStrategy
+    int onConflict() default OnConflictStrategy.ABORT;
+}
diff --git a/room/compiler/SQLite.g4 b/room/compiler/SQLite.g4
new file mode 100644
index 0000000..8eb895d
--- /dev/null
+++ b/room/compiler/SQLite.g4
@@ -0,0 +1,911 @@
+/*
+ * The MIT License (MIT)
+ *
+ * Copyright (c) 2014 by Bart Kiers
+ *
+ * Permission is hereby granted, free of charge, to any person
+ * obtaining a copy of this software and associated documentation
+ * files (the "Software"), to deal in the Software without
+ * restriction, including without limitation the rights to use,
+ * copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following
+ * conditions:
+ *
+ * The above copyright notice and this permission notice shall be
+ * included in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+ * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
+ * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+ * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
+ * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
+ * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
+ * OTHER DEALINGS IN THE SOFTWARE.
+ *
+ * Project      : sqlite-parser; an ANTLR4 grammar for SQLite
+ *                https://github.com/bkiers/sqlite-parser
+ * Developed by : Bart Kiers, bart@big-o.nl
+ */
+grammar SQLite;
+
+parse
+ : ( sql_stmt_list | error )* EOF
+ ;
+
+error
+ : UNEXPECTED_CHAR 
+   { 
+     throw new RuntimeException("UNEXPECTED_CHAR=" + $UNEXPECTED_CHAR.text); 
+   }
+ ;
+
+sql_stmt_list
+ : ';'* sql_stmt ( ';'+ sql_stmt )* ';'*
+ ;
+
+sql_stmt
+ : ( K_EXPLAIN ( K_QUERY K_PLAN )? )? ( alter_table_stmt
+                                      | analyze_stmt
+                                      | attach_stmt
+                                      | begin_stmt
+                                      | commit_stmt
+                                      | compound_select_stmt
+                                      | create_index_stmt
+                                      | create_table_stmt
+                                      | create_trigger_stmt
+                                      | create_view_stmt
+                                      | create_virtual_table_stmt
+                                      | delete_stmt
+                                      | delete_stmt_limited
+                                      | detach_stmt
+                                      | drop_index_stmt
+                                      | drop_table_stmt
+                                      | drop_trigger_stmt
+                                      | drop_view_stmt
+                                      | factored_select_stmt
+                                      | insert_stmt
+                                      | pragma_stmt
+                                      | reindex_stmt
+                                      | release_stmt
+                                      | rollback_stmt
+                                      | savepoint_stmt
+                                      | simple_select_stmt
+                                      | select_stmt
+                                      | update_stmt
+                                      | update_stmt_limited
+                                      | vacuum_stmt )
+ ;
+
+alter_table_stmt
+ : K_ALTER K_TABLE ( database_name '.' )? table_name
+   ( K_RENAME K_TO new_table_name
+   | K_ADD K_COLUMN? column_def
+   )
+ ;
+
+analyze_stmt
+ : K_ANALYZE ( database_name | table_or_index_name | database_name '.' table_or_index_name )?
+ ;
+
+attach_stmt
+ : K_ATTACH K_DATABASE? expr K_AS database_name
+ ;
+
+begin_stmt
+ : K_BEGIN ( K_DEFERRED | K_IMMEDIATE | K_EXCLUSIVE )? ( K_TRANSACTION transaction_name? )?
+ ;
+
+commit_stmt
+ : ( K_COMMIT | K_END ) ( K_TRANSACTION transaction_name? )?
+ ;
+
+compound_select_stmt
+ : with_clause?
+   select_core ( ( K_UNION K_ALL? | K_INTERSECT | K_EXCEPT ) select_core )+
+   ( K_ORDER K_BY ordering_term ( ',' ordering_term )* )?
+   ( K_LIMIT expr ( ( K_OFFSET | ',' ) expr )? )?
+ ;
+
+create_index_stmt
+ : K_CREATE K_UNIQUE? K_INDEX ( K_IF K_NOT K_EXISTS )?
+   ( database_name '.' )? index_name K_ON table_name '(' indexed_column ( ',' indexed_column )* ')'
+   ( K_WHERE expr )?
+ ;
+
+create_table_stmt
+ : K_CREATE ( K_TEMP | K_TEMPORARY )? K_TABLE ( K_IF K_NOT K_EXISTS )?
+   ( database_name '.' )? table_name
+   ( '(' column_def ( ',' column_def )*? ( ',' table_constraint )* ')' ( K_WITHOUT IDENTIFIER )?
+   | K_AS select_stmt
+   )
+ ;
+
+create_trigger_stmt
+ : K_CREATE ( K_TEMP | K_TEMPORARY )? K_TRIGGER ( K_IF K_NOT K_EXISTS )?
+   ( database_name '.' )? trigger_name ( K_BEFORE  | K_AFTER | K_INSTEAD K_OF )?
+   ( K_DELETE | K_INSERT | K_UPDATE ( K_OF column_name ( ',' column_name )* )? ) K_ON ( database_name '.' )? table_name
+   ( K_FOR K_EACH K_ROW )? ( K_WHEN expr )?
+   K_BEGIN ( ( update_stmt | insert_stmt | delete_stmt | select_stmt ) ';' )+ K_END
+ ;
+
+create_view_stmt
+ : K_CREATE ( K_TEMP | K_TEMPORARY )? K_VIEW ( K_IF K_NOT K_EXISTS )?
+   ( database_name '.' )? view_name K_AS select_stmt
+ ;
+
+create_virtual_table_stmt
+ : K_CREATE K_VIRTUAL K_TABLE ( K_IF K_NOT K_EXISTS )?
+   ( database_name '.' )? table_name
+   K_USING module_name ( '(' module_argument ( ',' module_argument )* ')' )?
+ ;
+
+delete_stmt
+ : with_clause? K_DELETE K_FROM qualified_table_name
+   ( K_WHERE expr )?
+ ;
+
+delete_stmt_limited
+ : with_clause? K_DELETE K_FROM qualified_table_name
+   ( K_WHERE expr )?
+   ( ( K_ORDER K_BY ordering_term ( ',' ordering_term )* )?
+     K_LIMIT expr ( ( K_OFFSET | ',' ) expr )?
+   )?
+ ;
+
+detach_stmt
+ : K_DETACH K_DATABASE? database_name
+ ;
+
+drop_index_stmt
+ : K_DROP K_INDEX ( K_IF K_EXISTS )? ( database_name '.' )? index_name
+ ;
+
+drop_table_stmt
+ : K_DROP K_TABLE ( K_IF K_EXISTS )? ( database_name '.' )? table_name
+ ;
+
+drop_trigger_stmt
+ : K_DROP K_TRIGGER ( K_IF K_EXISTS )? ( database_name '.' )? trigger_name
+ ;
+
+drop_view_stmt
+ : K_DROP K_VIEW ( K_IF K_EXISTS )? ( database_name '.' )? view_name
+ ;
+
+factored_select_stmt
+ : with_clause?
+   select_core ( compound_operator select_core )*
+   ( K_ORDER K_BY ordering_term ( ',' ordering_term )* )?
+   ( K_LIMIT expr ( ( K_OFFSET | ',' ) expr )? )?
+ ;
+
+insert_stmt
+ : with_clause? ( K_INSERT
+                | K_REPLACE
+                | K_INSERT K_OR K_REPLACE
+                | K_INSERT K_OR K_ROLLBACK
+                | K_INSERT K_OR K_ABORT
+                | K_INSERT K_OR K_FAIL
+                | K_INSERT K_OR K_IGNORE ) K_INTO
+   ( database_name '.' )? table_name ( '(' column_name ( ',' column_name )* ')' )?
+   ( K_VALUES '(' expr ( ',' expr )* ')' ( ',' '(' expr ( ',' expr )* ')' )*
+   | select_stmt
+   | K_DEFAULT K_VALUES
+   )
+ ;
+
+pragma_stmt
+ : K_PRAGMA ( database_name '.' )? pragma_name ( '=' pragma_value
+                                               | '(' pragma_value ')' )?
+ ;
+
+reindex_stmt
+ : K_REINDEX ( collation_name
+             | ( database_name '.' )? ( table_name | index_name )
+             )?
+ ;
+
+release_stmt
+ : K_RELEASE K_SAVEPOINT? savepoint_name
+ ;
+
+rollback_stmt
+ : K_ROLLBACK ( K_TRANSACTION transaction_name? )? ( K_TO K_SAVEPOINT? savepoint_name )?
+ ;
+
+savepoint_stmt
+ : K_SAVEPOINT savepoint_name
+ ;
+
+simple_select_stmt
+ : with_clause?
+   select_core ( K_ORDER K_BY ordering_term ( ',' ordering_term )* )?
+   ( K_LIMIT expr ( ( K_OFFSET | ',' ) expr )? )?
+ ;
+
+select_stmt
+ : with_clause?
+   select_or_values ( compound_operator select_or_values )*
+   ( K_ORDER K_BY ordering_term ( ',' ordering_term )* )?
+   ( K_LIMIT expr ( ( K_OFFSET | ',' ) expr )? )?
+ ;
+
+select_or_values
+ : K_SELECT ( K_DISTINCT | K_ALL )? result_column ( ',' result_column )*
+   ( K_FROM ( table_or_subquery ( ',' table_or_subquery )* | join_clause ) )?
+   ( K_WHERE expr )?
+   ( K_GROUP K_BY expr ( ',' expr )* ( K_HAVING expr )? )?
+ | K_VALUES '(' expr ( ',' expr )* ')' ( ',' '(' expr ( ',' expr )* ')' )*
+ ;
+
+update_stmt
+ : with_clause? K_UPDATE ( K_OR K_ROLLBACK
+                         | K_OR K_ABORT
+                         | K_OR K_REPLACE
+                         | K_OR K_FAIL
+                         | K_OR K_IGNORE )? qualified_table_name
+   K_SET column_name '=' expr ( ',' column_name '=' expr )* ( K_WHERE expr )?
+ ;
+
+update_stmt_limited
+ : with_clause? K_UPDATE ( K_OR K_ROLLBACK
+                         | K_OR K_ABORT
+                         | K_OR K_REPLACE
+                         | K_OR K_FAIL
+                         | K_OR K_IGNORE )? qualified_table_name
+   K_SET column_name '=' expr ( ',' column_name '=' expr )* ( K_WHERE expr )?
+   ( ( K_ORDER K_BY ordering_term ( ',' ordering_term )* )?
+     K_LIMIT expr ( ( K_OFFSET | ',' ) expr )?
+   )?
+ ;
+
+vacuum_stmt
+ : K_VACUUM
+ ;
+
+column_def
+ : column_name type_name? column_constraint*
+ ;
+
+type_name
+ : name+? ( '(' signed_number ')'
+         | '(' signed_number ',' signed_number ')' )?
+ ;
+
+column_constraint
+ : ( K_CONSTRAINT name )?
+   ( K_PRIMARY K_KEY ( K_ASC | K_DESC )? conflict_clause K_AUTOINCREMENT?
+   | K_NOT? K_NULL conflict_clause
+   | K_UNIQUE conflict_clause
+   | K_CHECK '(' expr ')'
+   | K_DEFAULT (signed_number | literal_value | '(' expr ')')
+   | K_COLLATE collation_name
+   | foreign_key_clause
+   )
+ ;
+
+conflict_clause
+ : ( K_ON K_CONFLICT ( K_ROLLBACK
+                     | K_ABORT
+                     | K_FAIL
+                     | K_IGNORE
+                     | K_REPLACE
+                     )
+   )?
+ ;
+
+/*
+    SQLite understands the following binary operators, in order from highest to
+    lowest precedence:
+
+    ||
+    *    /    %
+    +    -
+    <<   >>   &    |
+    <    <=   >    >=
+    =    ==   !=   <>   IS   IS NOT   IN   LIKE   GLOB   MATCH   REGEXP
+    AND
+    OR
+*/
+expr
+ : literal_value
+ | BIND_PARAMETER
+ | ( ( database_name '.' )? table_name '.' )? column_name
+ | unary_operator expr
+ | expr '||' expr
+ | expr ( '*' | '/' | '%' ) expr
+ | expr ( '+' | '-' ) expr
+ | expr ( '<<' | '>>' | '&' | '|' ) expr
+ | expr ( '<' | '<=' | '>' | '>=' ) expr
+ | expr ( '=' | '==' | '!=' | '<>' ) expr
+ | expr K_AND expr
+ | expr K_OR expr
+ | function_name '(' ( K_DISTINCT? expr ( ',' expr )* | '*' )? ')'
+ | '(' expr ')'
+ | K_CAST '(' expr K_AS type_name ')'
+ | expr K_COLLATE collation_name
+ | expr K_NOT? ( K_LIKE | K_GLOB | K_REGEXP | K_MATCH ) expr ( K_ESCAPE expr )?
+ | expr ( K_ISNULL | K_NOTNULL | K_NOT K_NULL )
+ | expr K_IS K_NOT? expr
+ | expr K_NOT? K_BETWEEN expr K_AND expr
+ | expr K_NOT? K_IN ( '(' ( select_stmt
+                          | expr ( ',' expr )*
+                          )?
+                      ')'
+                    | ( database_name '.' )? table_name )
+ | ( ( K_NOT )? K_EXISTS )? '(' select_stmt ')'
+ | K_CASE expr? ( K_WHEN expr K_THEN expr )+ ( K_ELSE expr )? K_END
+ | raise_function
+ ;
+
+foreign_key_clause
+ : K_REFERENCES foreign_table ( '(' column_name ( ',' column_name )* ')' )?
+   ( ( K_ON ( K_DELETE | K_UPDATE ) ( K_SET K_NULL
+                                    | K_SET K_DEFAULT
+                                    | K_CASCADE
+                                    | K_RESTRICT
+                                    | K_NO K_ACTION )
+     | K_MATCH name
+     )
+   )*
+   ( K_NOT? K_DEFERRABLE ( K_INITIALLY K_DEFERRED | K_INITIALLY K_IMMEDIATE )? )?
+ ;
+
+raise_function
+ : K_RAISE '(' ( K_IGNORE
+               | ( K_ROLLBACK | K_ABORT | K_FAIL ) ',' error_message )
+           ')'
+ ;
+
+indexed_column
+ : column_name ( K_COLLATE collation_name )? ( K_ASC | K_DESC )?
+ ;
+
+table_constraint
+ : ( K_CONSTRAINT name )?
+   ( ( K_PRIMARY K_KEY | K_UNIQUE ) '(' indexed_column ( ',' indexed_column )* ')' conflict_clause
+   | K_CHECK '(' expr ')'
+   | K_FOREIGN K_KEY '(' column_name ( ',' column_name )* ')' foreign_key_clause
+   )
+ ;
+
+with_clause
+ : K_WITH K_RECURSIVE? common_table_expression ( ',' common_table_expression )*
+ ;
+
+qualified_table_name
+ : ( database_name '.' )? table_name ( K_INDEXED K_BY index_name
+                                     | K_NOT K_INDEXED )?
+ ;
+
+ordering_term
+ : expr ( K_COLLATE collation_name )? ( K_ASC | K_DESC )?
+ ;
+
+pragma_value
+ : signed_number
+ | name
+ | STRING_LITERAL
+ ;
+
+common_table_expression
+ : table_name ( '(' column_name ( ',' column_name )* ')' )? K_AS '(' select_stmt ')'
+ ;
+
+result_column
+ : '*'
+ | table_name '.' '*'
+ | expr ( K_AS? column_alias )?
+ ;
+
+table_or_subquery
+ : ( schema_name '.' )? table_name ( K_AS? table_alias )?
+   ( K_INDEXED K_BY index_name
+   | K_NOT K_INDEXED )?
+ | ( schema_name '.' )? table_function_name '(' ( expr ( ',' expr )* )? ')' ( K_AS? table_alias )?
+ | '(' ( table_or_subquery ( ',' table_or_subquery )*
+       | join_clause )
+   ')'
+ | '(' select_stmt ')' ( K_AS? table_alias )?
+ ;
+
+join_clause
+ : table_or_subquery ( join_operator table_or_subquery join_constraint )*
+ ;
+
+join_operator
+ : ','
+ | K_NATURAL? ( K_LEFT K_OUTER? | K_INNER | K_CROSS )? K_JOIN
+ ;
+
+join_constraint
+ : ( K_ON expr
+   | K_USING '(' column_name ( ',' column_name )* ')' )?
+ ;
+
+select_core
+ : K_SELECT ( K_DISTINCT | K_ALL )? result_column ( ',' result_column )*
+   ( K_FROM ( table_or_subquery ( ',' table_or_subquery )* | join_clause ) )?
+   ( K_WHERE expr )?
+   ( K_GROUP K_BY expr ( ',' expr )* ( K_HAVING expr )? )?
+ | K_VALUES '(' expr ( ',' expr )* ')' ( ',' '(' expr ( ',' expr )* ')' )*
+ ;
+
+compound_operator
+ : K_UNION
+ | K_UNION K_ALL
+ | K_INTERSECT
+ | K_EXCEPT
+ ;
+
+signed_number
+ : ( '+' | '-' )? NUMERIC_LITERAL
+ ;
+
+literal_value
+ : NUMERIC_LITERAL
+ | STRING_LITERAL
+ | BLOB_LITERAL
+ | K_NULL
+ | K_CURRENT_TIME
+ | K_CURRENT_DATE
+ | K_CURRENT_TIMESTAMP
+ ;
+
+unary_operator
+ : '-'
+ | '+'
+ | '~'
+ | K_NOT
+ ;
+
+error_message
+ : STRING_LITERAL
+ ;
+
+module_argument // TODO check what exactly is permitted here
+ : expr
+ | column_def
+ ;
+
+column_alias
+ : IDENTIFIER
+ | STRING_LITERAL
+ ;
+
+keyword
+ : K_ABORT
+ | K_ACTION
+ | K_ADD
+ | K_AFTER
+ | K_ALL
+ | K_ALTER
+ | K_ANALYZE
+ | K_AND
+ | K_AS
+ | K_ASC
+ | K_ATTACH
+ | K_AUTOINCREMENT
+ | K_BEFORE
+ | K_BEGIN
+ | K_BETWEEN
+ | K_BY
+ | K_CASCADE
+ | K_CASE
+ | K_CAST
+ | K_CHECK
+ | K_COLLATE
+ | K_COLUMN
+ | K_COMMIT
+ | K_CONFLICT
+ | K_CONSTRAINT
+ | K_CREATE
+ | K_CROSS
+ | K_CURRENT_DATE
+ | K_CURRENT_TIME
+ | K_CURRENT_TIMESTAMP
+ | K_DATABASE
+ | K_DEFAULT
+ | K_DEFERRABLE
+ | K_DEFERRED
+ | K_DELETE
+ | K_DESC
+ | K_DETACH
+ | K_DISTINCT
+ | K_DROP
+ | K_EACH
+ | K_ELSE
+ | K_END
+ | K_ESCAPE
+ | K_EXCEPT
+ | K_EXCLUSIVE
+ | K_EXISTS
+ | K_EXPLAIN
+ | K_FAIL
+ | K_FOR
+ | K_FOREIGN
+ | K_FROM
+ | K_FULL
+ | K_GLOB
+ | K_GROUP
+ | K_HAVING
+ | K_IF
+ | K_IGNORE
+ | K_IMMEDIATE
+ | K_IN
+ | K_INDEX
+ | K_INDEXED
+ | K_INITIALLY
+ | K_INNER
+ | K_INSERT
+ | K_INSTEAD
+ | K_INTERSECT
+ | K_INTO
+ | K_IS
+ | K_ISNULL
+ | K_JOIN
+ | K_KEY
+ | K_LEFT
+ | K_LIKE
+ | K_LIMIT
+ | K_MATCH
+ | K_NATURAL
+ | K_NO
+ | K_NOT
+ | K_NOTNULL
+ | K_NULL
+ | K_OF
+ | K_OFFSET
+ | K_ON
+ | K_OR
+ | K_ORDER
+ | K_OUTER
+ | K_PLAN
+ | K_PRAGMA
+ | K_PRIMARY
+ | K_QUERY
+ | K_RAISE
+ | K_RECURSIVE
+ | K_REFERENCES
+ | K_REGEXP
+ | K_REINDEX
+ | K_RELEASE
+ | K_RENAME
+ | K_REPLACE
+ | K_RESTRICT
+ | K_RIGHT
+ | K_ROLLBACK
+ | K_ROW
+ | K_SAVEPOINT
+ | K_SELECT
+ | K_SET
+ | K_TABLE
+ | K_TEMP
+ | K_TEMPORARY
+ | K_THEN
+ | K_TO
+ | K_TRANSACTION
+ | K_TRIGGER
+ | K_UNION
+ | K_UNIQUE
+ | K_UPDATE
+ | K_USING
+ | K_VACUUM
+ | K_VALUES
+ | K_VIEW
+ | K_VIRTUAL
+ | K_WHEN
+ | K_WHERE
+ | K_WITH
+ | K_WITHOUT
+ ;
+
+// TODO check all names below
+
+name
+ : any_name
+ ;
+
+function_name
+ : any_name
+ ;
+
+database_name
+ : any_name
+ ;
+
+schema_name
+ : any_name
+ ;
+
+table_function_name
+ : any_name
+ ;
+
+table_name
+ : any_name
+ ;
+
+table_or_index_name
+ : any_name
+ ;
+
+new_table_name
+ : any_name
+ ;
+
+column_name
+ : any_name
+ ;
+
+collation_name
+ : any_name
+ ;
+
+foreign_table
+ : any_name
+ ;
+
+index_name
+ : any_name
+ ;
+
+trigger_name
+ : any_name
+ ;
+
+view_name
+ : any_name
+ ;
+
+module_name
+ : any_name
+ ;
+
+pragma_name
+ : any_name
+ ;
+
+savepoint_name
+ : any_name
+ ;
+
+table_alias
+ : IDENTIFIER
+ | STRING_LITERAL
+ | '(' table_alias ')'
+ ;
+
+transaction_name
+ : any_name
+ ;
+
+any_name
+ : IDENTIFIER
+ | keyword
+ | STRING_LITERAL
+ | '(' any_name ')'
+ ;
+
+SCOL : ';';
+DOT : '.';
+OPEN_PAR : '(';
+CLOSE_PAR : ')';
+COMMA : ',';
+ASSIGN : '=';
+STAR : '*';
+PLUS : '+';
+MINUS : '-';
+TILDE : '~';
+PIPE2 : '||';
+DIV : '/';
+MOD : '%';
+LT2 : '<<';
+GT2 : '>>';
+AMP : '&';
+PIPE : '|';
+LT : '<';
+LT_EQ : '<=';
+GT : '>';
+GT_EQ : '>=';
+EQ : '==';
+NOT_EQ1 : '!=';
+NOT_EQ2 : '<>';
+
+// http://www.sqlite.org/lang_keywords.html
+K_ABORT : A B O R T;
+K_ACTION : A C T I O N;
+K_ADD : A D D;
+K_AFTER : A F T E R;
+K_ALL : A L L;
+K_ALTER : A L T E R;
+K_ANALYZE : A N A L Y Z E;
+K_AND : A N D;
+K_AS : A S;
+K_ASC : A S C;
+K_ATTACH : A T T A C H;
+K_AUTOINCREMENT : A U T O I N C R E M E N T;
+K_BEFORE : B E F O R E;
+K_BEGIN : B E G I N;
+K_BETWEEN : B E T W E E N;
+K_BY : B Y;
+K_CASCADE : C A S C A D E;
+K_CASE : C A S E;
+K_CAST : C A S T;
+K_CHECK : C H E C K;
+K_COLLATE : C O L L A T E;
+K_COLUMN : C O L U M N;
+K_COMMIT : C O M M I T;
+K_CONFLICT : C O N F L I C T;
+K_CONSTRAINT : C O N S T R A I N T;
+K_CREATE : C R E A T E;
+K_CROSS : C R O S S;
+K_CURRENT_DATE : C U R R E N T '_' D A T E;
+K_CURRENT_TIME : C U R R E N T '_' T I M E;
+K_CURRENT_TIMESTAMP : C U R R E N T '_' T I M E S T A M P;
+K_DATABASE : D A T A B A S E;
+K_DEFAULT : D E F A U L T;
+K_DEFERRABLE : D E F E R R A B L E;
+K_DEFERRED : D E F E R R E D;
+K_DELETE : D E L E T E;
+K_DESC : D E S C;
+K_DETACH : D E T A C H;
+K_DISTINCT : D I S T I N C T;
+K_DROP : D R O P;
+K_EACH : E A C H;
+K_ELSE : E L S E;
+K_END : E N D;
+K_ESCAPE : E S C A P E;
+K_EXCEPT : E X C E P T;
+K_EXCLUSIVE : E X C L U S I V E;
+K_EXISTS : E X I S T S;
+K_EXPLAIN : E X P L A I N;
+K_FAIL : F A I L;
+K_FOR : F O R;
+K_FOREIGN : F O R E I G N;
+K_FROM : F R O M;
+K_FULL : F U L L;
+K_GLOB : G L O B;
+K_GROUP : G R O U P;
+K_HAVING : H A V I N G;
+K_IF : I F;
+K_IGNORE : I G N O R E;
+K_IMMEDIATE : I M M E D I A T E;
+K_IN : I N;
+K_INDEX : I N D E X;
+K_INDEXED : I N D E X E D;
+K_INITIALLY : I N I T I A L L Y;
+K_INNER : I N N E R;
+K_INSERT : I N S E R T;
+K_INSTEAD : I N S T E A D;
+K_INTERSECT : I N T E R S E C T;
+K_INTO : I N T O;
+K_IS : I S;
+K_ISNULL : I S N U L L;
+K_JOIN : J O I N;
+K_KEY : K E Y;
+K_LEFT : L E F T;
+K_LIKE : L I K E;
+K_LIMIT : L I M I T;
+K_MATCH : M A T C H;
+K_NATURAL : N A T U R A L;
+K_NO : N O;
+K_NOT : N O T;
+K_NOTNULL : N O T N U L L;
+K_NULL : N U L L;
+K_OF : O F;
+K_OFFSET : O F F S E T;
+K_ON : O N;
+K_OR : O R;
+K_ORDER : O R D E R;
+K_OUTER : O U T E R;
+K_PLAN : P L A N;
+K_PRAGMA : P R A G M A;
+K_PRIMARY : P R I M A R Y;
+K_QUERY : Q U E R Y;
+K_RAISE : R A I S E;
+K_RECURSIVE : R E C U R S I V E;
+K_REFERENCES : R E F E R E N C E S;
+K_REGEXP : R E G E X P;
+K_REINDEX : R E I N D E X;
+K_RELEASE : R E L E A S E;
+K_RENAME : R E N A M E;
+K_REPLACE : R E P L A C E;
+K_RESTRICT : R E S T R I C T;
+K_RIGHT : R I G H T;
+K_ROLLBACK : R O L L B A C K;
+K_ROW : R O W;
+K_SAVEPOINT : S A V E P O I N T;
+K_SELECT : S E L E C T;
+K_SET : S E T;
+K_TABLE : T A B L E;
+K_TEMP : T E M P;
+K_TEMPORARY : T E M P O R A R Y;
+K_THEN : T H E N;
+K_TO : T O;
+K_TRANSACTION : T R A N S A C T I O N;
+K_TRIGGER : T R I G G E R;
+K_UNION : U N I O N;
+K_UNIQUE : U N I Q U E;
+K_UPDATE : U P D A T E;
+K_USING : U S I N G;
+K_VACUUM : V A C U U M;
+K_VALUES : V A L U E S;
+K_VIEW : V I E W;
+K_VIRTUAL : V I R T U A L;
+K_WHEN : W H E N;
+K_WHERE : W H E R E;
+K_WITH : W I T H;
+K_WITHOUT : W I T H O U T;
+
+IDENTIFIER
+ : '"' (~'"' | '""')* '"'
+ | '`' (~'`' | '``')* '`'
+ | '[' ~']'* ']'
+ | [a-zA-Z_] [a-zA-Z_0-9]* // TODO check: needs more chars in set
+ ;
+
+NUMERIC_LITERAL
+ : DIGIT+ ( '.' DIGIT* )? ( E [-+]? DIGIT+ )?
+ | '.' DIGIT+ ( E [-+]? DIGIT+ )?
+ ;
+
+BIND_PARAMETER
+ : '?' DIGIT*
+ | [:@$] IDENTIFIER
+ ;
+
+STRING_LITERAL
+ : '\'' ( ~'\'' | '\'\'' )* '\''
+ ;
+
+BLOB_LITERAL
+ : X STRING_LITERAL
+ ;
+
+SINGLE_LINE_COMMENT
+ : '--' ~[\r\n]* -> channel(HIDDEN)
+ ;
+
+MULTILINE_COMMENT
+ : '/*' .*? ( '*/' | EOF ) -> channel(HIDDEN)
+ ;
+
+SPACES
+ : [ \u000B\t\r\n] -> channel(HIDDEN)
+ ;
+
+UNEXPECTED_CHAR
+ : .
+ ;
+
+fragment DIGIT : [0-9];
+
+fragment A : [aA];
+fragment B : [bB];
+fragment C : [cC];
+fragment D : [dD];
+fragment E : [eE];
+fragment F : [fF];
+fragment G : [gG];
+fragment H : [hH];
+fragment I : [iI];
+fragment J : [jJ];
+fragment K : [kK];
+fragment L : [lL];
+fragment M : [mM];
+fragment N : [nN];
+fragment O : [oO];
+fragment P : [pP];
+fragment Q : [qQ];
+fragment R : [rR];
+fragment S : [sS];
+fragment T : [tT];
+fragment U : [uU];
+fragment V : [vV];
+fragment W : [wW];
+fragment X : [xX];
+fragment Y : [yY];
+fragment Z : [zZ];
\ No newline at end of file
diff --git a/room/compiler/build.gradle b/room/compiler/build.gradle
new file mode 100644
index 0000000..b189e44
--- /dev/null
+++ b/room/compiler/build.gradle
@@ -0,0 +1,69 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+apply plugin: 'kotlin'
+apply plugin: 'maven'
+
+def antlrOut = "$buildDir/generated/antlr/grammar-gen/"
+sourceSets {
+    main.java.srcDirs += 'src/main/grammar-gen'
+    test.java.srcDirs += 'src/tests/kotlin'
+    main.java.srcDirs += antlrOut
+}
+project.ext.noDocs = true
+dependencies {
+    // taken from ButterKnife
+    def logger = new com.android.build.gradle.internal.LoggerWrapper(project.logger)
+    def sdkHandler = new com.android.build.gradle.internal.SdkHandler(project, logger)
+    compile project(":room:common")
+    compile project(":room:migration")
+    compile libs.kotlin.stdlib
+    compile libs.auto_common
+    compile libs.javapoet
+    compile libs.antlr
+    compile libs.xerial
+    compile libs.apache.commons.codec
+    testCompile libs.google_compile_testing
+    testCompile libs.junit
+    testCompile libs.ij_annotations
+    testCompile libs.mockito_core
+    testCompile fileTree(dir: "${sdkHandler.sdkFolder}/platforms/android-$tools.current_sdk/",
+            include : "android.jar")
+    testCompile fileTree(dir: "${new File(project(":room:runtime").buildDir, "libJar")}",
+            include : "*.jar")
+    testCompile fileTree(dir: "${new File(project(":room:db").buildDir, "libJar")}",
+            include : "*.jar")
+    testCompile files(org.gradle.internal.jvm.Jvm.current().getToolsJar())
+}
+
+archivesBaseName = "compiler"
+
+def generateAntlrTask = task('generateAntlrGrammar', type: JavaExec) {
+    def outFolder = file(antlrOut)
+    outputs.dir(outFolder)
+    inputs.dir("$projectDir/SQLite.g4")
+    classpath configurations.runtime
+    main "org.antlr.v4.Tool"
+    args "SQLite.g4", "-visitor", "-o", new File(outFolder, "android/arch/persistence/room/parser").path,
+            "-package", "android.arch.persistence.room.parser"
+}
+
+tasks.findByName("compileKotlin").dependsOn(generateAntlrTask)
+tasks.findByName("compileKotlin").dependsOn(":room:runtime:jarDebug")
+tasks.findByName("compileKotlin").dependsOn(":room:db:jarDebug")
+
+createKotlinCheckstyle(project)
+
diff --git a/room/compiler/src/main/kotlin/android/arch/persistence/room/RoomProcessor.kt b/room/compiler/src/main/kotlin/android/arch/persistence/room/RoomProcessor.kt
new file mode 100644
index 0000000..78849eb
--- /dev/null
+++ b/room/compiler/src/main/kotlin/android/arch/persistence/room/RoomProcessor.kt
@@ -0,0 +1,126 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.arch.persistence.room
+
+import android.arch.persistence.room.processor.Context
+import android.arch.persistence.room.processor.DatabaseProcessor
+import android.arch.persistence.room.processor.ProcessorErrors
+import android.arch.persistence.room.vo.DaoMethod
+import android.arch.persistence.room.vo.Warning
+import android.arch.persistence.room.writer.DaoWriter
+import android.arch.persistence.room.writer.DatabaseWriter
+import com.google.auto.common.BasicAnnotationProcessor
+import com.google.auto.common.MoreElements
+import com.google.common.collect.SetMultimap
+import java.io.File
+import javax.annotation.processing.SupportedSourceVersion
+import javax.lang.model.SourceVersion
+import javax.lang.model.element.Element
+
+/**
+ * The annotation processor for Room.
+ */
+@SupportedSourceVersion(SourceVersion.RELEASE_7)
+class RoomProcessor : BasicAnnotationProcessor() {
+    override fun initSteps(): MutableIterable<ProcessingStep>? {
+        val context = Context(processingEnv)
+        return arrayListOf(DatabaseProcessingStep(context))
+    }
+
+    override fun getSupportedOptions(): MutableSet<String> {
+        return Context.ARG_OPTIONS.toMutableSet()
+    }
+
+    class DatabaseProcessingStep(context: Context) : ContextBoundProcessingStep(context) {
+        override fun process(elementsByAnnotation: SetMultimap<Class<out Annotation>, Element>)
+                : MutableSet<Element> {
+            // TODO multi step support
+            val databases = elementsByAnnotation[Database::class.java]
+                    ?.map {
+                        DatabaseProcessor(context, MoreElements.asType(it)).process()
+                    }
+            val allDaoMethods = databases?.flatMap { it.daoMethods }
+            allDaoMethods?.let {
+                prepareDaosForWriting(databases!!, it)
+                it.forEach {
+                    DaoWriter(it.dao, context.processingEnv).write(context.processingEnv)
+                }
+            }
+
+            databases?.forEach { db ->
+                DatabaseWriter(db).write(context.processingEnv)
+                if (db.exportSchema) {
+                    val schemaOutFolder = context.schemaOutFolder
+                    if (schemaOutFolder == null) {
+                        context.logger.w(Warning.MISSING_SCHEMA_LOCATION, db.element,
+                                ProcessorErrors.MISSING_SCHEMA_EXPORT_DIRECTORY)
+                    } else {
+                        if (!schemaOutFolder.exists()) {
+                            schemaOutFolder.mkdirs()
+                        }
+                        val qName = db.element.qualifiedName.toString()
+                        val dbSchemaFolder = File(schemaOutFolder, qName)
+                        if (!dbSchemaFolder.exists()) {
+                            dbSchemaFolder.mkdirs()
+                        }
+                        db.exportSchema(File(dbSchemaFolder, "${db.version}.json"))
+                    }
+                }
+            }
+            context.databaseVerifier?.let {
+                it.closeConnection()
+            }
+            return mutableSetOf()
+        }
+        override fun annotations(): MutableSet<out Class<out Annotation>> {
+            return mutableSetOf(Database::class.java, Dao::class.java, Entity::class.java)
+        }
+
+        /**
+         * Traverses all dao methods and assigns them suffix if they are used in multiple databases.
+         */
+        private fun prepareDaosForWriting(
+                databases: List<android.arch.persistence.room.vo.Database>,
+                daoMethods: List<DaoMethod>) {
+            daoMethods.groupBy { it.dao.typeName }
+                    // if used only in 1 database, nothing to do.
+                    .filter { entry -> entry.value.size > 1 }
+                    .forEach { entry ->
+                        entry.value.groupBy { daoMethod ->
+                            // first suffix guess: Database's simple name
+                            val db = databases.first { db -> db.daoMethods.contains(daoMethod) }
+                            db.typeName.simpleName()
+                        }.forEach { entry ->
+                            val dbName = entry.key
+                            val methods = entry.value
+                            if (methods.size == 1) {
+                                //good, db names do not clash, use db name as suffix
+                                methods.first().dao.setSuffix(dbName)
+                            } else {
+                                // ok looks like a dao is used in 2 different databases both of
+                                // which have the same name. enumerate.
+                                methods.forEachIndexed { index, method ->
+                                    method.dao.setSuffix("${dbName}_$index")
+                                }
+                            }
+                        }
+                    }
+        }
+    }
+
+    abstract class ContextBoundProcessingStep(val context: Context) : ProcessingStep
+}
diff --git a/room/compiler/src/main/kotlin/android/arch/persistence/room/ext/element_ext.kt b/room/compiler/src/main/kotlin/android/arch/persistence/room/ext/element_ext.kt
new file mode 100644
index 0000000..87846e3
--- /dev/null
+++ b/room/compiler/src/main/kotlin/android/arch/persistence/room/ext/element_ext.kt
@@ -0,0 +1,167 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+@file:Suppress("PLATFORM_CLASS_MAPPED_TO_KOTLIN")
+
+package android.arch.persistence.room.ext
+
+import com.google.auto.common.AnnotationMirrors
+import com.google.auto.common.MoreElements
+import com.google.auto.common.MoreTypes
+import javax.annotation.processing.ProcessingEnvironment
+import javax.lang.model.element.AnnotationValue
+import javax.lang.model.element.Element
+import javax.lang.model.element.ElementKind
+import javax.lang.model.element.Modifier
+import javax.lang.model.element.TypeElement
+import javax.lang.model.element.VariableElement
+import javax.lang.model.type.TypeKind
+import javax.lang.model.type.TypeMirror
+import javax.lang.model.util.SimpleAnnotationValueVisitor6
+import kotlin.reflect.KClass
+
+fun Element.hasAnyOf(vararg modifiers: Modifier): Boolean {
+    return this.modifiers.any { modifiers.contains(it) }
+}
+
+fun Element.hasAnnotation(klass: KClass<out Annotation>): Boolean {
+    return MoreElements.isAnnotationPresent(this, klass.java)
+}
+
+/**
+ * Checks if it has all of the annotations
+ */
+fun Element.hasAllOf(vararg klasses: KClass<out Annotation>): Boolean {
+    return !klasses.any { !hasAnnotation(it) }
+}
+
+/**
+ * gets all members including super privates. does not handle duplicate field names!!!
+ */
+// TODO handle conflicts with super: b/35568142
+fun TypeElement.getAllFieldsIncludingPrivateSupers(processingEnvironment: ProcessingEnvironment):
+        Set<VariableElement> {
+    val myMembers = processingEnvironment.elementUtils.getAllMembers(this)
+            .filter { it.kind == ElementKind.FIELD }
+            .filter { it is VariableElement }
+            .map { it as VariableElement }
+            .toSet()
+    if (superclass.kind != TypeKind.NONE) {
+        return myMembers + MoreTypes.asTypeElement(superclass)
+                .getAllFieldsIncludingPrivateSupers(processingEnvironment)
+    } else {
+        return myMembers
+    }
+}
+
+// code below taken from dagger2
+// compiler/src/main/java/dagger/internal/codegen/ConfigurationAnnotations.java
+private val TO_LIST_OF_TYPES = object
+    : SimpleAnnotationValueVisitor6<List<TypeMirror>, Void?>() {
+    override fun visitArray(values: MutableList<out AnnotationValue>?, p: Void?)
+            : List<TypeMirror> {
+        return values?.map {
+            val tmp = TO_TYPE.visit(it)
+            tmp
+        }?.filterNotNull() ?: emptyList()
+    }
+
+
+    override fun defaultAction(o: Any?, p: Void?): List<TypeMirror>? {
+        return emptyList()
+    }
+}
+
+private val TO_TYPE = object : SimpleAnnotationValueVisitor6<TypeMirror, Void>() {
+
+    override fun visitType(t: TypeMirror, p: Void?): TypeMirror {
+        return t
+    }
+
+    override fun defaultAction(o: Any?, p: Void?): TypeMirror {
+        throw TypeNotPresentException(o!!.toString(), null)
+    }
+}
+
+fun AnnotationValue.toListOfClassTypes(): List<TypeMirror> {
+    return TO_LIST_OF_TYPES.visit(this)
+}
+
+fun AnnotationValue.toType(): TypeMirror {
+    return TO_TYPE.visit(this)
+}
+
+fun AnnotationValue.toClassType(): TypeMirror? {
+    return TO_TYPE.visit(this)
+}
+
+fun TypeMirror.isCollection(): Boolean {
+    return MoreTypes.isType(this)
+            && (MoreTypes.isTypeOf(java.util.List::class.java, this)
+            || MoreTypes.isTypeOf(java.util.Set::class.java, this))
+}
+
+fun Element.getAnnotationValue(annotation: Class<out Annotation>, fieldName: String): Any? {
+    return MoreElements.getAnnotationMirror(this, annotation)
+            .orNull()?.let {
+        AnnotationMirrors.getAnnotationValue(it, fieldName)?.value
+    }
+}
+
+private val ANNOTATION_VALUE_TO_INT_VISITOR = object : SimpleAnnotationValueVisitor6<Int?, Void>() {
+    override fun visitInt(i: Int, p: Void?): Int? {
+        return i
+    }
+}
+
+private val ANNOTATION_VALUE_TO_BOOLEAN_VISITOR = object
+    : SimpleAnnotationValueVisitor6<Boolean?, Void>() {
+    override fun visitBoolean(b: Boolean, p: Void?): Boolean? {
+        return b
+    }
+}
+
+private val ANNOTATION_VALUE_TO_STRING_VISITOR = object
+    : SimpleAnnotationValueVisitor6<String?, Void>() {
+    override fun visitString(s: String?, p: Void?): String? {
+        return s
+    }
+}
+
+private val ANNOTATION_VALUE_STRING_ARR_VISITOR = object
+    : SimpleAnnotationValueVisitor6<List<String>, Void>() {
+    override fun visitArray(vals: MutableList<out AnnotationValue>?, p: Void?): List<String> {
+        return vals?.map {
+            ANNOTATION_VALUE_TO_STRING_VISITOR.visit(it)
+        }?.filterNotNull() ?: emptyList()
+    }
+}
+
+fun AnnotationValue.getAsInt(def: Int? = null): Int? {
+    return ANNOTATION_VALUE_TO_INT_VISITOR.visit(this) ?: def
+}
+
+fun AnnotationValue.getAsString(def: String? = null): String? {
+    return ANNOTATION_VALUE_TO_STRING_VISITOR.visit(this) ?: def
+}
+
+fun AnnotationValue.getAsBoolean(def: Boolean): Boolean {
+    return ANNOTATION_VALUE_TO_BOOLEAN_VISITOR.visit(this) ?: def
+}
+
+fun AnnotationValue.getAsStringList(): List<String> {
+    return ANNOTATION_VALUE_STRING_ARR_VISITOR.visit(this)
+}
diff --git a/room/compiler/src/main/kotlin/android/arch/persistence/room/ext/javapoet_ext.kt b/room/compiler/src/main/kotlin/android/arch/persistence/room/ext/javapoet_ext.kt
new file mode 100644
index 0000000..72b9427
--- /dev/null
+++ b/room/compiler/src/main/kotlin/android/arch/persistence/room/ext/javapoet_ext.kt
@@ -0,0 +1,120 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.arch.persistence.room.ext
+
+import com.squareup.javapoet.ArrayTypeName
+import com.squareup.javapoet.ClassName
+import com.squareup.javapoet.TypeName
+import javax.lang.model.type.TypeMirror
+import kotlin.reflect.KClass
+
+val L = "\$L"
+val T = "\$T"
+val N = "\$N"
+val S = "\$S"
+
+fun KClass<*>.typeName() = ClassName.get(this.java)
+fun KClass<*>.arrayTypeName() = ArrayTypeName.of(typeName())
+fun TypeMirror.typeName() = TypeName.get(this)
+
+object SupportDbTypeNames {
+    val DB: ClassName = ClassName.get("android.arch.persistence.db", "SupportSQLiteDatabase")
+    val SQLITE_STMT : ClassName =
+            ClassName.get("android.arch.persistence.db", "SupportSQLiteStatement")
+    val SQLITE_OPEN_HELPER : ClassName =
+            ClassName.get("android.arch.persistence.db", "SupportSQLiteOpenHelper")
+    val SQLITE_OPEN_HELPER_CALLBACK : ClassName =
+            ClassName.get("android.arch.persistence.db", "SupportSQLiteOpenHelper.Callback")
+    val SQLITE_OPEN_HELPER_FACTORY : ClassName =
+            ClassName.get("android.arch.persistence.db", "SupportSQLiteOpenHelper.Factory")
+    val SQLITE_OPEN_HELPER_CONFIG : ClassName =
+            ClassName.get("android.arch.persistence.db", "SupportSQLiteOpenHelper.Configuration")
+    val SQLITE_OPEN_HELPER_CONFIG_BUILDER : ClassName =
+            ClassName.get("android.arch.persistence.db",
+                    "SupportSQLiteOpenHelper.Configuration.Builder")
+}
+
+object RoomTypeNames {
+    val STRING_UTIL: ClassName = ClassName.get("android.arch.persistence.room.util", "StringUtil")
+    val CURSOR_CONVERTER : ClassName =
+            ClassName.get("android.arch.persistence.room", "CursorConverter")
+    val ROOM : ClassName = ClassName.get("android.arch.persistence.room", "Room")
+    val ROOM_DB : ClassName = ClassName.get("android.arch.persistence.room", "RoomDatabase")
+    val ROOM_DB_CONFIG : ClassName = ClassName.get("android.arch.persistence.room",
+            "DatabaseConfiguration")
+    val INSERTION_ADAPTER : ClassName =
+            ClassName.get("android.arch.persistence.room", "EntityInsertionAdapter")
+    val DELETE_OR_UPDATE_ADAPTER : ClassName =
+            ClassName.get("android.arch.persistence.room", "EntityDeletionOrUpdateAdapter")
+    val SHARED_SQLITE_STMT : ClassName =
+            ClassName.get("android.arch.persistence.room", "SharedSQLiteStatement")
+    val INVALIDATION_TRACKER : ClassName =
+            ClassName.get("android.arch.persistence.room", "InvalidationTracker")
+    val INVALIDATION_OBSERVER : ClassName =
+            ClassName.get("android.arch.persistence.room.InvalidationTracker", "Observer")
+    val ROOM_SQL_QUERY : ClassName =
+            ClassName.get("android.arch.persistence.room", "RoomSQLiteQuery")
+    val OPEN_HELPER : ClassName =
+            ClassName.get("android.arch.persistence.room", "RoomOpenHelper")
+    val OPEN_HELPER_DELEGATE: ClassName =
+            ClassName.get("android.arch.persistence.room", "RoomOpenHelper.Delegate")
+    val TABLE_INFO : ClassName =
+            ClassName.get("android.arch.persistence.room.util", "TableInfo")
+    val TABLE_INFO_COLUMN : ClassName =
+            ClassName.get("android.arch.persistence.room.util", "TableInfo.Column")
+    val TABLE_INFO_FOREIGN_KEY : ClassName =
+            ClassName.get("android.arch.persistence.room.util", "TableInfo.ForeignKey")
+}
+
+object LifecyclesTypeNames {
+    val LIVE_DATA: ClassName = ClassName.get("android.arch.lifecycle", "LiveData")
+    val COMPUTABLE_LIVE_DATA : ClassName = ClassName.get("android.arch.lifecycle",
+            "ComputableLiveData")
+}
+
+object AndroidTypeNames {
+    val CURSOR : ClassName = ClassName.get("android.database", "Cursor")
+    val ARRAY_MAP : ClassName = ClassName.get("android.support.v4.util", "ArrayMap")
+}
+
+object CommonTypeNames {
+    val LIST = ClassName.get("java.util", "List")
+    val SET = ClassName.get("java.util", "Set")
+    val STRING = ClassName.get("java.lang", "String")
+}
+
+object RxJava2TypeNames {
+    val FLOWABLE = ClassName.get("io.reactivex", "Flowable")
+}
+
+object ReactiveStreamsTypeNames {
+    val PUBLISHER = ClassName.get("org.reactivestreams", "Publisher")
+}
+
+object RoomRxJava2TypeNames {
+    val RX_ROOM = ClassName.get("android.arch.persistence.room", "RxRoom")
+}
+
+fun TypeName.defaultValue() : String {
+    return if (!isPrimitive) {
+        "null"
+    } else if (this == TypeName.BOOLEAN) {
+        "false"
+    } else {
+        "0"
+    }
+}
diff --git a/room/compiler/src/main/kotlin/android/arch/persistence/room/ext/string_ext.kt b/room/compiler/src/main/kotlin/android/arch/persistence/room/ext/string_ext.kt
new file mode 100644
index 0000000..9b07e27
--- /dev/null
+++ b/room/compiler/src/main/kotlin/android/arch/persistence/room/ext/string_ext.kt
@@ -0,0 +1,48 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+private fun String.toCamelCase() : String {
+    val split = this.split("_")
+    if (split.isEmpty()) return ""
+    if (split.size == 1) return split[0].capitalize()
+    return split.joinToCamelCase()
+}
+
+private fun String.toCamelCaseAsVar() : String {
+    val split = this.split("_")
+    if (split.isEmpty()) return ""
+    if (split.size == 1) return split[0]
+    return split.joinToCamelCaseAsVar()
+}
+
+private fun List<String>.joinToCamelCase(): String = when(size) {
+    0 -> throw IllegalArgumentException("invalid section size, cannot be zero")
+    1 -> this[0].toCamelCase()
+    else -> this.map {it.toCamelCase()}.joinToString("")
+}
+
+private fun List<String>.joinToCamelCaseAsVar(): String = when(size) {
+    0 -> throw IllegalArgumentException("invalid section size, cannot be zero")
+    1 -> this[0].toCamelCaseAsVar()
+    else -> get(0).toCamelCaseAsVar() + drop(1).joinToCamelCase()
+}
+
+private val javaCharRegex = "[^a-zA-Z0-9]".toRegex()
+fun String.stripNonJava(): String {
+    return this.split(javaCharRegex)
+            .map(String::trim)
+            .joinToCamelCaseAsVar()
+}
diff --git a/room/compiler/src/main/kotlin/android/arch/persistence/room/ext/type_mirror_ext.kt b/room/compiler/src/main/kotlin/android/arch/persistence/room/ext/type_mirror_ext.kt
new file mode 100644
index 0000000..97cd051
--- /dev/null
+++ b/room/compiler/src/main/kotlin/android/arch/persistence/room/ext/type_mirror_ext.kt
@@ -0,0 +1,32 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+import javax.lang.model.type.TypeKind.BOOLEAN
+import javax.lang.model.type.TypeKind.BYTE
+import javax.lang.model.type.TypeKind.CHAR
+import javax.lang.model.type.TypeKind.DOUBLE
+import javax.lang.model.type.TypeKind.FLOAT
+import javax.lang.model.type.TypeKind.INT
+import javax.lang.model.type.TypeKind.LONG
+import javax.lang.model.type.TypeKind.SHORT
+import javax.lang.model.type.TypeMirror
+
+fun TypeMirror.defaultValue() : String {
+    return when(this.kind) {
+        BOOLEAN -> "false"
+        BYTE, SHORT, INT, LONG, CHAR, FLOAT, DOUBLE -> "0"
+        else -> "null"
+    }
+}
diff --git a/room/compiler/src/main/kotlin/android/arch/persistence/room/log/RLog.kt b/room/compiler/src/main/kotlin/android/arch/persistence/room/log/RLog.kt
new file mode 100644
index 0000000..10614ce
--- /dev/null
+++ b/room/compiler/src/main/kotlin/android/arch/persistence/room/log/RLog.kt
@@ -0,0 +1,102 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+@file:Suppress("unused")
+
+package android.arch.persistence.room.log
+
+import android.arch.persistence.room.vo.Warning
+import java.util.UnknownFormatConversionException
+import javax.annotation.processing.ProcessingEnvironment
+import javax.lang.model.element.Element
+import javax.tools.Diagnostic
+import javax.tools.Diagnostic.Kind.ERROR
+import javax.tools.Diagnostic.Kind.NOTE
+import javax.tools.Diagnostic.Kind.WARNING
+
+class RLog(val messager : Messager, val suppressedWarnings : Set<Warning>,
+           val defaultElement : Element?) {
+    private fun String.safeFormat(vararg args: Any): String {
+        try {
+            return format(args)
+        } catch (ex: UnknownFormatConversionException) {
+            // the input string might be from random source in which case we rather print the
+            // msg as is instead of crashing while reporting an error.
+            return this
+        }
+    }
+
+    fun d(element: Element, msg: String, vararg args: Any) {
+        messager.printMessage(NOTE, msg.safeFormat(args), element)
+    }
+
+    fun d(msg: String, vararg args: Any) {
+        messager.printMessage(NOTE, msg.safeFormat(args))
+    }
+
+    fun e(element: Element, msg: String, vararg args: Any) {
+        messager.printMessage(ERROR, msg.safeFormat(args), element)
+    }
+
+    fun e(msg: String, vararg args: Any) {
+        messager.printMessage(ERROR, msg.safeFormat(args), defaultElement)
+    }
+
+    fun w(warning: Warning, element: Element? = null, msg: String, vararg args: Any) {
+        if (suppressedWarnings.contains(warning)) {
+            return
+        }
+        messager.printMessage(WARNING, msg.safeFormat(args),
+                element ?: defaultElement)
+    }
+
+    fun w(warning: Warning, msg: String, vararg args: Any) {
+        if (suppressedWarnings.contains(warning)) {
+            return
+        }
+        messager.printMessage(WARNING, msg.safeFormat(args), defaultElement)
+    }
+
+    interface Messager {
+        fun printMessage(kind: Diagnostic.Kind, msg: String, element: Element? = null)
+    }
+
+    class ProcessingEnvMessager(val processingEnv: ProcessingEnvironment) : Messager {
+        override fun printMessage(kind: Diagnostic.Kind, msg: String, element: Element?) {
+            processingEnv.messager.printMessage(kind, msg, element)
+        }
+    }
+
+    class CollectingMessager : Messager {
+        private val messages = mutableMapOf<Diagnostic.Kind, MutableList<Pair<String, Element?>>> ()
+        override fun printMessage(kind: Diagnostic.Kind, msg: String, element: Element?) {
+            messages.getOrPut(kind, {
+                arrayListOf<Pair<String, Element?>>()
+            }).add(Pair(msg, element))
+        }
+
+        fun hasErrors() = messages.containsKey(Diagnostic.Kind.ERROR)
+
+        fun writeTo(env : ProcessingEnvironment) {
+            messages.forEach { pair ->
+                val kind = pair.key
+                pair.value.forEach {
+                    env.messager.printMessage(kind, it.first, it.second)
+                }
+            }
+        }
+    }
+}
diff --git a/room/compiler/src/main/kotlin/android/arch/persistence/room/parser/ParsedQuery.kt b/room/compiler/src/main/kotlin/android/arch/persistence/room/parser/ParsedQuery.kt
new file mode 100644
index 0000000..85d578d
--- /dev/null
+++ b/room/compiler/src/main/kotlin/android/arch/persistence/room/parser/ParsedQuery.kt
@@ -0,0 +1,125 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.arch.persistence.room.parser
+
+import android.arch.persistence.room.parser.SectionType.BIND_VAR
+import android.arch.persistence.room.parser.SectionType.NEWLINE
+import android.arch.persistence.room.parser.SectionType.TEXT
+import android.arch.persistence.room.verifier.QueryResultInfo
+import org.antlr.v4.runtime.tree.TerminalNode
+
+enum class SectionType {
+    BIND_VAR,
+    TEXT,
+    NEWLINE
+}
+
+data class Section(val text: String, val type: SectionType) {
+    companion object {
+        fun text(text: String) = Section(text, SectionType.TEXT)
+        fun newline() = Section("", SectionType.NEWLINE)
+        fun bindVar(text: String) = Section(text, SectionType.BIND_VAR)
+    }
+}
+
+data class Table(val name: String, val alias: String)
+
+data class ParsedQuery(val original: String, val type: QueryType,
+                       val inputs: List<TerminalNode>,
+                       // pairs of table name and alias,
+                       val tables: Set<Table>,
+                       val syntaxErrors: List<String>) {
+    companion object {
+        val STARTS_WITH_NUMBER = "^\\?[0-9]".toRegex()
+        val MISSING = ParsedQuery("missing query", QueryType.UNKNOWN, emptyList(), emptySet(),
+                emptyList())
+    }
+
+    /**
+     * Optional data that might be assigned when the query is parsed inside an annotation processor.
+     * User may turn this off or it might be disabled for any reason so generated code should
+     * always handle not having it.
+     */
+    var resultInfo: QueryResultInfo? = null
+
+    val sections by lazy {
+        val lines = original.lines()
+        val inputsByLine = inputs.groupBy { it.symbol.line }
+        val sections = arrayListOf<Section>()
+        lines.forEachIndexed { index, line ->
+            var charInLine = 0
+            inputsByLine[index + 1]?.forEach { bindVar ->
+                if (charInLine < bindVar.symbol.charPositionInLine) {
+                    sections.add(Section.text(line.substring(charInLine,
+                            bindVar.symbol.charPositionInLine)))
+                }
+                sections.add(Section.bindVar(bindVar.text))
+                charInLine = bindVar.symbol.charPositionInLine + bindVar.symbol.text.length
+            }
+            if (charInLine < line.length) {
+                sections.add(Section.text(line.substring(charInLine)))
+            }
+            if (index + 1 < lines.size) {
+                sections.add(Section.newline())
+            }
+        }
+        sections
+    }
+
+    val bindSections by lazy { sections.filter { it.type == BIND_VAR } }
+
+    private fun unnamedVariableErrors(): List<String> {
+        val anonymousBindError = if (inputs.any { it.text == "?" }) {
+            arrayListOf(ParserErrors.ANONYMOUS_BIND_ARGUMENT)
+        } else {
+            emptyList<String>()
+        }
+        return anonymousBindError + inputs.filter {
+            it.text.matches(STARTS_WITH_NUMBER)
+        }.map {
+            ParserErrors.cannotUseVariableIndices(it.text, it.symbol.charPositionInLine)
+        }
+    }
+
+    private fun unknownQueryTypeErrors(): List<String> {
+        return if (QueryType.SUPPORTED.contains(type)) {
+            emptyList()
+        } else {
+            listOf(ParserErrors.invalidQueryType(type))
+        }
+    }
+
+    val errors by lazy {
+        if (syntaxErrors.isNotEmpty()) {
+            // if there is a syntax error, don't report others since they might be misleading.
+            syntaxErrors
+        } else {
+            unnamedVariableErrors() + unknownQueryTypeErrors()
+        }
+    }
+
+    val queryWithReplacedBindParams by lazy {
+        sections.joinToString("") {
+            when (it.type) {
+                TEXT -> it.text
+                BIND_VAR -> "?"
+                NEWLINE -> "\n"
+                else -> throw IllegalArgumentException("??")
+            }
+        }
+    }
+}
diff --git a/room/compiler/src/main/kotlin/android/arch/persistence/room/parser/ParserErrors.kt b/room/compiler/src/main/kotlin/android/arch/persistence/room/parser/ParserErrors.kt
new file mode 100644
index 0000000..e3b4f40
--- /dev/null
+++ b/room/compiler/src/main/kotlin/android/arch/persistence/room/parser/ParserErrors.kt
@@ -0,0 +1,33 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.arch.persistence.room.parser
+
+object ParserErrors {
+    val ANONYMOUS_BIND_ARGUMENT = "Room does not support ? as bind parameters. You must use" +
+            " named bind arguments (e..g :argName)"
+
+    val NOT_ONE_QUERY = "Must have exactly 1 query in @Query value"
+
+    fun invalidQueryType(type: QueryType): String {
+        return "$type query type is not supported yet. You can use:" +
+                QueryType.SUPPORTED.joinToString(", ") { it.name }
+    }
+
+    fun cannotUseVariableIndices(name: String, position: Int) = "Cannot use variable indices." +
+            " Use named parameters instead (e.g. WHERE name LIKE :nameArg and lastName LIKE " +
+            ":lastName). Problem: $name at $position"
+}
diff --git a/room/compiler/src/main/kotlin/android/arch/persistence/room/parser/SqlParser.kt b/room/compiler/src/main/kotlin/android/arch/persistence/room/parser/SqlParser.kt
new file mode 100644
index 0000000..7bdd397
--- /dev/null
+++ b/room/compiler/src/main/kotlin/android/arch/persistence/room/parser/SqlParser.kt
@@ -0,0 +1,201 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.arch.persistence.room.parser
+
+import android.arch.persistence.room.ColumnInfo
+import org.antlr.v4.runtime.ANTLRInputStream
+import org.antlr.v4.runtime.BaseErrorListener
+import org.antlr.v4.runtime.CommonTokenStream
+import org.antlr.v4.runtime.RecognitionException
+import org.antlr.v4.runtime.Recognizer
+import org.antlr.v4.runtime.tree.ParseTree
+import org.antlr.v4.runtime.tree.TerminalNode
+import java.util.ArrayList
+import javax.annotation.processing.ProcessingEnvironment
+import javax.lang.model.type.TypeKind
+import javax.lang.model.type.TypeMirror
+
+class QueryVisitor(val original: String, val syntaxErrors: ArrayList<String>,
+                   statement: ParseTree) : SQLiteBaseVisitor<Void?>() {
+    val bindingExpressions = arrayListOf<TerminalNode>()
+    // table name alias mappings
+    val tableNames = mutableSetOf<Table>()
+    val queryType: QueryType
+
+    init {
+        queryType = (0..statement.childCount - 1).map {
+            findQueryType(statement.getChild(it))
+        }.filterNot { it == QueryType.UNKNOWN }.firstOrNull() ?: QueryType.UNKNOWN
+
+        statement.accept(this)
+    }
+
+    private fun findQueryType(statement: ParseTree): QueryType {
+        return when (statement) {
+            is SQLiteParser.Factored_select_stmtContext,
+            is SQLiteParser.Compound_select_stmtContext,
+            is SQLiteParser.Select_stmtContext,
+            is SQLiteParser.Simple_select_stmtContext ->
+                QueryType.SELECT
+
+            is SQLiteParser.Delete_stmt_limitedContext,
+            is SQLiteParser.Delete_stmtContext ->
+                QueryType.DELETE
+
+            is SQLiteParser.Insert_stmtContext ->
+                QueryType.INSERT
+            is SQLiteParser.Update_stmtContext,
+            is SQLiteParser.Update_stmt_limitedContext ->
+                QueryType.UPDATE
+            is TerminalNode -> when (statement.text) {
+                "EXPLAIN" -> QueryType.EXPLAIN
+                else -> QueryType.UNKNOWN
+            }
+            else -> QueryType.UNKNOWN
+        }
+    }
+
+    override fun visitExpr(ctx: SQLiteParser.ExprContext): Void? {
+        val bindParameter = ctx.BIND_PARAMETER()
+        if (bindParameter != null) {
+            bindingExpressions.add(bindParameter)
+        }
+        return super.visitExpr(ctx)
+    }
+
+    fun createParsedQuery(): ParsedQuery {
+        return ParsedQuery(original,
+                queryType,
+                bindingExpressions.sortedBy { it.sourceInterval.a },
+                tableNames,
+                syntaxErrors)
+    }
+
+    override fun visitTable_or_subquery(ctx: SQLiteParser.Table_or_subqueryContext): Void? {
+        val tableName = ctx.table_name()?.text
+        if (tableName != null) {
+            val tableAlias = ctx.table_alias()?.text
+            tableNames.add(Table(unescapeIdentifier(tableName),
+                    unescapeIdentifier(tableAlias ?: tableName)))
+        }
+        return super.visitTable_or_subquery(ctx)
+    }
+
+    private fun unescapeIdentifier(text : String) : String {
+        val trimmed = text.trim()
+        if (trimmed.startsWith("`") && trimmed.endsWith('`')) {
+            return unescapeIdentifier(trimmed.substring(1, trimmed.length - 1))
+        }
+        if (trimmed.startsWith('"') && trimmed.endsWith('"')) {
+            return unescapeIdentifier(trimmed.substring(1, trimmed.length - 1))
+        }
+        return trimmed
+    }
+}
+
+class SqlParser {
+    companion object {
+        fun parse(input: String): ParsedQuery {
+            val inputStream = ANTLRInputStream(input)
+            val lexer = SQLiteLexer(inputStream)
+            val tokenStream = CommonTokenStream(lexer)
+            val parser = SQLiteParser(tokenStream)
+            val syntaxErrors = arrayListOf<String>()
+            parser.addErrorListener(object : BaseErrorListener() {
+                override fun syntaxError(recognizer: Recognizer<*, *>, offendingSymbol: Any,
+                                         line: Int, charPositionInLine: Int, msg: String,
+                                         e: RecognitionException?) {
+                    syntaxErrors.add(msg)
+                }
+            })
+            try {
+                val parsed = parser.parse()
+                val statementList = parsed.sql_stmt_list()
+                if (statementList.isEmpty()) {
+                    syntaxErrors.add(ParserErrors.NOT_ONE_QUERY)
+                    return ParsedQuery(input, QueryType.UNKNOWN, emptyList(), emptySet(),
+                            listOf(ParserErrors.NOT_ONE_QUERY))
+                }
+                val statements = statementList.first().children
+                        .filter { it is SQLiteParser.Sql_stmtContext }
+                if (statements.size != 1) {
+                    syntaxErrors.add(ParserErrors.NOT_ONE_QUERY)
+                }
+                val statement = statements.first()
+                return QueryVisitor(input, syntaxErrors, statement).createParsedQuery()
+            } catch (antlrError: RuntimeException) {
+                return ParsedQuery(input, QueryType.UNKNOWN, emptyList(), emptySet(),
+                        listOf("unknown error while parsing $input : ${antlrError.message}"))
+            }
+        }
+    }
+}
+
+enum class QueryType {
+    UNKNOWN,
+    SELECT,
+    DELETE,
+    UPDATE,
+    EXPLAIN,
+    INSERT;
+
+    companion object {
+        // IF you change this, don't forget to update @Query documentation.
+        val SUPPORTED = hashSetOf(SELECT, DELETE, UPDATE)
+    }
+}
+
+enum class SQLTypeAffinity {
+    NULL,
+    TEXT,
+    INTEGER,
+    REAL,
+    BLOB;
+    fun getTypeMirrors(env : ProcessingEnvironment) : List<TypeMirror>? {
+        val typeUtils = env.typeUtils
+        return when(this) {
+            TEXT -> listOf(env.elementUtils.getTypeElement("java.lang.String").asType())
+            INTEGER -> withBoxedTypes(env, TypeKind.INT, TypeKind.BYTE, TypeKind.CHAR,
+                    TypeKind.BOOLEAN, TypeKind.LONG, TypeKind.SHORT)
+            REAL -> withBoxedTypes(env, TypeKind.DOUBLE, TypeKind.FLOAT)
+            BLOB -> listOf(typeUtils.getArrayType(
+                    typeUtils.getPrimitiveType(TypeKind.BYTE)))
+            else -> emptyList()
+        }
+    }
+
+    private fun withBoxedTypes(env : ProcessingEnvironment, vararg primitives : TypeKind) :
+            List<TypeMirror> {
+        return primitives.flatMap {
+            val primitiveType = env.typeUtils.getPrimitiveType(it)
+            listOf(primitiveType, env.typeUtils.boxedClass(primitiveType).asType())
+        }
+    }
+
+    companion object {
+        // converts from ColumnInfo#SQLiteTypeAffinity
+        fun fromAnnotationValue(value : Int) : SQLTypeAffinity? {
+            return when(value) {
+                ColumnInfo.BLOB -> BLOB
+                ColumnInfo.INTEGER -> INTEGER
+                ColumnInfo.REAL -> REAL
+                ColumnInfo.TEXT -> TEXT
+                else -> null
+            }
+        }
+    }
+}
diff --git a/room/compiler/src/main/kotlin/android/arch/persistence/room/preconditions/Checks.kt b/room/compiler/src/main/kotlin/android/arch/persistence/room/preconditions/Checks.kt
new file mode 100644
index 0000000..dad5c1c
--- /dev/null
+++ b/room/compiler/src/main/kotlin/android/arch/persistence/room/preconditions/Checks.kt
@@ -0,0 +1,69 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.arch.persistence.room.preconditions
+
+import android.arch.persistence.room.ext.hasAnnotation
+import android.arch.persistence.room.log.RLog
+import com.squareup.javapoet.ParameterizedTypeName
+import com.squareup.javapoet.TypeName
+import com.squareup.javapoet.TypeVariableName
+import javax.lang.model.element.Element
+import kotlin.reflect.KClass
+
+/**
+ * Similar to preconditions but element bound and just logs the error instead of throwing an
+ * exception.
+ * <p>
+ * It is important for processing to continue when some errors happen so that we can generate as
+ * much code as possible, leaving only the errors in javac output.
+ */
+class Checks(private val logger: RLog) {
+
+    fun check(predicate: Boolean, element: Element, errorMsg: String, vararg args: Any): Boolean {
+        if (!predicate) {
+            logger.e(element, errorMsg, args)
+        }
+        return predicate
+    }
+
+    fun hasAnnotation(element: Element, annotation: KClass<out Annotation>, errorMsg: String,
+                      vararg args: Any): Boolean {
+        return if (!element.hasAnnotation(annotation)) {
+            logger.e(element, errorMsg, args)
+            false
+        } else {
+            true
+        }
+    }
+
+    fun notUnbound(typeName: TypeName, element: Element, errorMsg: String,
+                   vararg args: Any): Boolean {
+        // TODO support bounds cases like <T extends Foo> T bar()
+        val failed = check(typeName !is TypeVariableName, element, errorMsg, args)
+        if (typeName is ParameterizedTypeName) {
+            val nestedFailure = typeName.typeArguments
+                    .map { notUnbound(it, element, errorMsg, args) }
+                    .any { it == true }
+            return !(failed || nestedFailure)
+        }
+        return !failed
+    }
+
+    fun notBlank(value: String?, element: Element, msg: String, vararg args: Any) : Boolean {
+        return check(value != null && value.isNotBlank(), element, msg, args)
+    }
+}
diff --git a/room/compiler/src/main/kotlin/android/arch/persistence/room/processor/Context.kt b/room/compiler/src/main/kotlin/android/arch/persistence/room/processor/Context.kt
new file mode 100644
index 0000000..4e84be3
--- /dev/null
+++ b/room/compiler/src/main/kotlin/android/arch/persistence/room/processor/Context.kt
@@ -0,0 +1,116 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.arch.persistence.room.processor
+
+import android.arch.persistence.room.log.RLog
+import android.arch.persistence.room.preconditions.Checks
+import android.arch.persistence.room.processor.cache.Cache
+import android.arch.persistence.room.solver.TypeAdapterStore
+import android.arch.persistence.room.verifier.DatabaseVerifier
+import java.io.File
+import java.util.LinkedHashSet
+import javax.annotation.processing.ProcessingEnvironment
+import javax.lang.model.element.Element
+import javax.lang.model.type.TypeMirror
+
+class Context private constructor(val processingEnv: ProcessingEnvironment,
+                                  val logger: RLog,
+                                  private val typeConverters
+                                        : CustomConverterProcessor.ProcessResult,
+                                  private val inheritedAdapterStore: TypeAdapterStore?,
+                                  val cache: Cache) {
+    val checker: Checks = Checks(logger)
+    val COMMON_TYPES: Context.CommonTypes = Context.CommonTypes(processingEnv)
+
+    val typeAdapterStore by lazy {
+        if (inheritedAdapterStore != null) {
+            TypeAdapterStore.copy(this, inheritedAdapterStore)
+        } else {
+            TypeAdapterStore.create(this, typeConverters.converters)
+        }
+    }
+
+    // set when database and its entities are processed.
+    var databaseVerifier: DatabaseVerifier? = null
+
+    companion object {
+        val ARG_OPTIONS by lazy {
+            ProcessorOptions.values().map { it.argName }
+        }
+    }
+
+    constructor(processingEnv: ProcessingEnvironment) : this(
+            processingEnv = processingEnv,
+            logger = RLog(RLog.ProcessingEnvMessager(processingEnv), emptySet(), null),
+            typeConverters = CustomConverterProcessor.ProcessResult.EMPTY,
+            inheritedAdapterStore = null,
+            cache = Cache(null, LinkedHashSet(), emptySet())) {
+    }
+
+    class CommonTypes(val processingEnv: ProcessingEnvironment) {
+        val STRING: TypeMirror by lazy {
+            processingEnv.elementUtils.getTypeElement("java.lang.String").asType()
+        }
+    }
+
+    val schemaOutFolder by lazy {
+        val arg = processingEnv.options[ProcessorOptions.OPTION_SCHEMA_FOLDER.argName]
+        if (arg?.isNotEmpty() ?: false) {
+            File(arg)
+        } else {
+            null
+        }
+    }
+
+    fun <T> collectLogs(handler: (Context) -> T): Pair<T, RLog.CollectingMessager> {
+        val collector = RLog.CollectingMessager()
+        val subContext = Context(processingEnv = processingEnv,
+                logger = RLog(collector, logger.suppressedWarnings, logger.defaultElement),
+                typeConverters = this.typeConverters,
+                inheritedAdapterStore = typeAdapterStore,
+                cache = cache)
+        subContext.databaseVerifier = databaseVerifier
+        val result = handler(subContext)
+        return Pair(result, collector)
+    }
+
+    fun fork(element: Element): Context {
+        val suppressedWarnings = SuppressWarningProcessor.getSuppressedWarnings(element)
+        val processConvertersResult = CustomConverterProcessor.findConverters(this, element)
+        val canReUseAdapterStore = processConvertersResult.classes.isEmpty()
+        // order here is important since the sub context should give priority to new converters.
+        val subTypeConverters = if (canReUseAdapterStore) {
+            this.typeConverters
+        } else {
+            processConvertersResult + this.typeConverters
+        }
+        val subSuppressedWarnings = suppressedWarnings + logger.suppressedWarnings
+        val subCache = Cache(cache, subTypeConverters.classes, subSuppressedWarnings)
+        val subContext = Context(
+                processingEnv = processingEnv,
+                logger = RLog(logger.messager, subSuppressedWarnings, element),
+                typeConverters = subTypeConverters,
+                inheritedAdapterStore = if (canReUseAdapterStore) typeAdapterStore else null,
+                cache = subCache)
+        subContext.databaseVerifier = databaseVerifier
+        return subContext
+    }
+
+    enum class ProcessorOptions(val argName: String) {
+        OPTION_SCHEMA_FOLDER("room.schemaLocation")
+    }
+}
diff --git a/room/compiler/src/main/kotlin/android/arch/persistence/room/processor/CustomConverterProcessor.kt b/room/compiler/src/main/kotlin/android/arch/persistence/room/processor/CustomConverterProcessor.kt
new file mode 100644
index 0000000..1ed245d
--- /dev/null
+++ b/room/compiler/src/main/kotlin/android/arch/persistence/room/processor/CustomConverterProcessor.kt
@@ -0,0 +1,149 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.arch.persistence.room.processor
+
+import android.arch.persistence.room.TypeConverter
+import android.arch.persistence.room.TypeConverters
+import android.arch.persistence.room.ext.hasAnnotation
+import android.arch.persistence.room.ext.hasAnyOf
+import android.arch.persistence.room.ext.toListOfClassTypes
+import android.arch.persistence.room.ext.typeName
+import android.arch.persistence.room.processor.ProcessorErrors.TYPE_CONVERTER_BAD_RETURN_TYPE
+import android.arch.persistence.room.processor.ProcessorErrors.TYPE_CONVERTER_EMPTY_CLASS
+import android.arch.persistence.room.processor.ProcessorErrors
+        .TYPE_CONVERTER_MISSING_NOARG_CONSTRUCTOR
+import android.arch.persistence.room.processor.ProcessorErrors.TYPE_CONVERTER_MUST_BE_PUBLIC
+import android.arch.persistence.room.processor.ProcessorErrors.TYPE_CONVERTER_MUST_RECEIVE_1_PARAM
+import android.arch.persistence.room.processor.ProcessorErrors.TYPE_CONVERTER_UNBOUND_GENERIC
+import android.arch.persistence.room.solver.types.CustomTypeConverterWrapper
+import android.arch.persistence.room.vo.CustomTypeConverter
+import com.google.auto.common.AnnotationMirrors
+import com.google.auto.common.MoreElements
+import com.google.auto.common.MoreTypes
+import java.util.LinkedHashSet
+import javax.lang.model.element.Element
+import javax.lang.model.element.ExecutableElement
+import javax.lang.model.element.Modifier
+import javax.lang.model.element.TypeElement
+import javax.lang.model.type.DeclaredType
+import javax.lang.model.type.TypeKind
+import javax.lang.model.type.TypeMirror
+import javax.lang.model.util.ElementFilter
+
+/**
+ * Processes classes that are referenced in TypeConverters annotations.
+ */
+class CustomConverterProcessor(val context: Context, val element: TypeElement) {
+    companion object {
+        private val INVALID_RETURN_TYPES = setOf(TypeKind.ERROR, TypeKind.VOID, TypeKind.NONE)
+        fun findConverters(context: Context, element: Element): ProcessResult {
+            val annotation = MoreElements.getAnnotationMirror(element,
+                    TypeConverters::class.java).orNull()
+            return annotation?.let {
+                val classes = AnnotationMirrors.getAnnotationValue(annotation, "value")
+                        ?.toListOfClassTypes()
+                        ?.filter {
+                            MoreTypes.isType(it)
+                        }?.mapTo(LinkedHashSet(), {it}) ?: LinkedHashSet<TypeMirror>()
+                val converters = classes
+                        .flatMap {
+                            CustomConverterProcessor(context, MoreTypes.asTypeElement(it))
+                                    .process()
+                        }
+                converters.let {
+                    reportDuplicates(context, converters)
+                }
+                ProcessResult(classes, converters.map(::CustomTypeConverterWrapper))
+            } ?: ProcessResult.EMPTY
+        }
+
+        fun reportDuplicates(context: Context, converters : List<CustomTypeConverter>) {
+            val groupedByFrom = converters.groupBy { it.from.typeName() }
+            groupedByFrom.forEach {
+                it.value.groupBy { it.to.typeName() }.forEach {
+                    if (it.value.size > 1) {
+                        it.value.forEach { converter ->
+                            context.logger.e(converter.method, ProcessorErrors
+                                    .duplicateTypeConverters(it.value.minus(converter)))
+                        }
+                    }
+                }
+            }
+        }
+    }
+
+    fun process(): List<CustomTypeConverter> {
+        // using element utils instead of MoreElements to include statics.
+        val methods = ElementFilter
+                .methodsIn(context.processingEnv.elementUtils.getAllMembers(element))
+        val declaredType = MoreTypes.asDeclared(element.asType())
+        val converterMethods = methods.filter {
+            it.hasAnnotation(TypeConverter::class)
+        }
+        context.checker.check(converterMethods.isNotEmpty(), element, TYPE_CONVERTER_EMPTY_CLASS)
+        val allStatic = converterMethods.all { it.modifiers.contains(Modifier.STATIC) }
+        val constructors = ElementFilter.constructorsIn(
+                context.processingEnv.elementUtils.getAllMembers(element))
+        context.checker.check(allStatic || constructors.isEmpty() || constructors.any {
+            it.parameters.isEmpty()
+        }, element, TYPE_CONVERTER_MISSING_NOARG_CONSTRUCTOR)
+        return converterMethods.map {
+            processMethod(declaredType, it)
+        }.filterNotNull()
+    }
+
+    private fun processMethod(container: DeclaredType, methodElement: ExecutableElement)
+            : CustomTypeConverter? {
+        val asMember = context.processingEnv.typeUtils.asMemberOf(container, methodElement)
+        val executableType = MoreTypes.asExecutable(asMember)
+        val returnType = executableType.returnType
+        val invalidReturnType = INVALID_RETURN_TYPES.contains(returnType.kind)
+        context.checker.check(methodElement.hasAnyOf(Modifier.PUBLIC), methodElement,
+                TYPE_CONVERTER_MUST_BE_PUBLIC)
+        if (invalidReturnType) {
+            context.logger.e(methodElement, TYPE_CONVERTER_BAD_RETURN_TYPE)
+            return null
+        }
+        val returnTypeName = returnType.typeName()
+        context.checker.notUnbound(returnTypeName, methodElement,
+                TYPE_CONVERTER_UNBOUND_GENERIC)
+        val params = methodElement.parameters
+        if (params.size != 1) {
+            context.logger.e(methodElement, TYPE_CONVERTER_MUST_RECEIVE_1_PARAM)
+            return null
+        }
+        val param = params.map {
+            MoreTypes.asMemberOf(context.processingEnv.typeUtils, container, it)
+        }.first()
+        context.checker.notUnbound(param.typeName(), params[0], TYPE_CONVERTER_UNBOUND_GENERIC)
+        return CustomTypeConverter(container, methodElement, param, returnType)
+    }
+
+    /**
+     * Order of classes is important hence they are a LinkedHashSet not a set.
+     */
+    open class ProcessResult(val classes: LinkedHashSet<TypeMirror>,
+                             val converters: List<CustomTypeConverterWrapper>) {
+        object EMPTY : ProcessResult(LinkedHashSet(), emptyList())
+        operator fun plus(other : ProcessResult) : ProcessResult {
+            val newClasses = LinkedHashSet<TypeMirror>()
+            newClasses.addAll(classes)
+            newClasses.addAll(other.classes)
+            return ProcessResult(newClasses, converters + other.converters)
+        }
+    }
+}
diff --git a/room/compiler/src/main/kotlin/android/arch/persistence/room/processor/DaoProcessor.kt b/room/compiler/src/main/kotlin/android/arch/persistence/room/processor/DaoProcessor.kt
new file mode 100644
index 0000000..0c0ff49
--- /dev/null
+++ b/room/compiler/src/main/kotlin/android/arch/persistence/room/processor/DaoProcessor.kt
@@ -0,0 +1,152 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.arch.persistence.room.processor
+
+import android.arch.persistence.room.Delete
+import android.arch.persistence.room.Insert
+import android.arch.persistence.room.Query
+import android.arch.persistence.room.SkipQueryVerification
+import android.arch.persistence.room.Update
+import android.arch.persistence.room.ext.hasAnnotation
+import android.arch.persistence.room.ext.hasAnyOf
+import android.arch.persistence.room.ext.typeName
+import android.arch.persistence.room.verifier.DatabaseVerifier
+import android.arch.persistence.room.vo.Dao
+import com.google.auto.common.MoreElements
+import com.google.auto.common.MoreTypes
+import com.squareup.javapoet.TypeName
+import javax.lang.model.element.ElementKind
+import javax.lang.model.element.ExecutableElement
+import javax.lang.model.element.Modifier.ABSTRACT
+import javax.lang.model.element.TypeElement
+import javax.lang.model.type.DeclaredType
+
+class DaoProcessor(baseContext : Context, val element: TypeElement, val dbType: DeclaredType,
+                   val dbVerifier : DatabaseVerifier?) {
+    val context = baseContext.fork(element)
+
+    companion object {
+        val PROCESSED_ANNOTATIONS = listOf(Insert::class, Delete::class, Query::class,
+                Update::class)
+    }
+
+    fun process() : Dao {
+        context.checker.hasAnnotation(element, android.arch.persistence.room.Dao::class,
+                ProcessorErrors.DAO_MUST_BE_ANNOTATED_WITH_DAO)
+        context.checker.check(element.hasAnyOf(ABSTRACT) || element.kind == ElementKind.INTERFACE,
+                element, ProcessorErrors.DAO_MUST_BE_AN_ABSTRACT_CLASS_OR_AN_INTERFACE)
+
+        val declaredType = MoreTypes.asDeclared(element.asType())
+        val allMembers = context.processingEnv.elementUtils.getAllMembers(element)
+        val methods = allMembers
+            .filter {
+                it.hasAnyOf(ABSTRACT) && it.kind == ElementKind.METHOD
+            }.map {
+                MoreElements.asExecutable(it)
+            }.groupBy { method ->
+                context.checker.check(
+                        PROCESSED_ANNOTATIONS.count { method.hasAnnotation(it) } == 1, method,
+                        ProcessorErrors.CANNOT_USE_MORE_THAN_ONE_DAO_METHOD_ANNOTATION
+                )
+                if (method.hasAnnotation(Query::class)) {
+                    Query::class
+                } else if (method.hasAnnotation(Insert::class)) {
+                    Insert::class
+                } else if (method.hasAnnotation(Delete::class)) {
+                    Delete::class
+                } else if (method.hasAnnotation(Update::class)) {
+                    Update::class
+                } else {
+                    Any::class
+                }
+            }
+        val processorVerifier = if (element.hasAnnotation(SkipQueryVerification::class)) {
+            null
+        } else {
+            dbVerifier
+        }
+
+        val queryMethods = methods[Query::class]?.map {
+            QueryMethodProcessor(
+                    baseContext = context,
+                    containing = declaredType,
+                    executableElement = it,
+                    dbVerifier = processorVerifier).process()
+        } ?: emptyList()
+
+        val insertionMethods = methods[Insert::class]?.map {
+            InsertionMethodProcessor(
+                    baseContext = context,
+                    containing = declaredType,
+                    executableElement = it).process()
+        } ?: emptyList()
+
+        val deletionMethods = methods[Delete::class]?.map {
+            DeletionMethodProcessor(
+                    baseContext = context,
+                    containing = declaredType,
+                    executableElement = it).process()
+        } ?: emptyList()
+
+        val updateMethods = methods[Update::class]?.map {
+            UpdateMethodProcessor(
+                    baseContext = context,
+                    containing = declaredType,
+                    executableElement = it).process()
+        } ?: emptyList()
+
+        val constructors = allMembers
+                .filter { it.kind == ElementKind.CONSTRUCTOR }
+                .map { MoreElements.asExecutable(it) }
+        val typeUtils = context.processingEnv.typeUtils
+        val goodConstructor = constructors
+                .filter {
+                    it.parameters.size == 1
+                            && typeUtils.isAssignable(dbType, it.parameters[0].asType())
+                }
+                .firstOrNull()
+        val constructorParamType = if (goodConstructor != null) {
+            goodConstructor.parameters[0].asType().typeName()
+        } else {
+            validateEmptyConstructor(constructors)
+            null
+        }
+
+        context.checker.check(methods[Any::class] == null, element,
+                ProcessorErrors.ABSTRACT_METHOD_IN_DAO_MISSING_ANY_ANNOTATION)
+
+        val type = TypeName.get(declaredType)
+        context.checker.notUnbound(type, element,
+                ProcessorErrors.CANNOT_USE_UNBOUND_GENERICS_IN_DAO_CLASSES)
+
+        return Dao(element = element,
+                type = declaredType,
+                queryMethods = queryMethods,
+                insertionMethods = insertionMethods,
+                deletionMethods = deletionMethods,
+                updateMethods = updateMethods,
+                constructorParamType = constructorParamType)
+    }
+
+    private fun validateEmptyConstructor(constructors: List<ExecutableElement>) {
+        if (constructors.isNotEmpty() && constructors.all { it.parameters.isNotEmpty() }) {
+            context.logger.e(element, ProcessorErrors.daoMustHaveMatchingConstructor(
+                    element.toString(), dbType.toString()))
+        }
+    }
+
+}
diff --git a/room/compiler/src/main/kotlin/android/arch/persistence/room/processor/DatabaseProcessor.kt b/room/compiler/src/main/kotlin/android/arch/persistence/room/processor/DatabaseProcessor.kt
new file mode 100644
index 0000000..9ebf6df
--- /dev/null
+++ b/room/compiler/src/main/kotlin/android/arch/persistence/room/processor/DatabaseProcessor.kt
@@ -0,0 +1,244 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.arch.persistence.room.processor
+
+import android.arch.persistence.room.SkipQueryVerification
+import android.arch.persistence.room.ext.RoomTypeNames
+import android.arch.persistence.room.ext.getAsBoolean
+import android.arch.persistence.room.ext.getAsInt
+import android.arch.persistence.room.ext.hasAnnotation
+import android.arch.persistence.room.ext.hasAnyOf
+import android.arch.persistence.room.ext.toListOfClassTypes
+import android.arch.persistence.room.verifier.DatabaseVerifier
+import android.arch.persistence.room.vo.Dao
+import android.arch.persistence.room.vo.DaoMethod
+import android.arch.persistence.room.vo.Database
+import android.arch.persistence.room.vo.Entity
+import com.google.auto.common.AnnotationMirrors
+import com.google.auto.common.MoreElements
+import com.google.auto.common.MoreTypes
+import com.squareup.javapoet.TypeName
+import javax.lang.model.element.AnnotationMirror
+import javax.lang.model.element.Element
+import javax.lang.model.element.ElementKind
+import javax.lang.model.element.Modifier
+import javax.lang.model.element.TypeElement
+import javax.lang.model.type.TypeMirror
+
+
+class DatabaseProcessor(baseContext: Context, val element: TypeElement) {
+    val context = baseContext.fork(element)
+
+    val baseClassElement: TypeMirror by lazy {
+        context.processingEnv.elementUtils.getTypeElement(
+                RoomTypeNames.ROOM_DB.packageName() + "." + RoomTypeNames.ROOM_DB.simpleName())
+                .asType()
+    }
+
+    fun process(): Database {
+        val dbAnnotation = MoreElements
+                .getAnnotationMirror(element, android.arch.persistence.room.Database::class.java)
+                .orNull()
+        val entities = processEntities(dbAnnotation, element)
+        validateUniqueTableNames(element, entities)
+        validateForeignKeys(element, entities)
+
+        val extendsRoomDb = context.processingEnv.typeUtils.isAssignable(
+                MoreElements.asType(element).asType(), baseClassElement)
+        context.checker.check(extendsRoomDb, element, ProcessorErrors.DB_MUST_EXTEND_ROOM_DB)
+
+        val allMembers = context.processingEnv.elementUtils.getAllMembers(element)
+
+        val dbVerifier = if (element.hasAnnotation(SkipQueryVerification::class)) {
+            null
+        } else {
+            DatabaseVerifier.create(context, element, entities)
+        }
+        context.databaseVerifier = dbVerifier
+
+        val declaredType = MoreTypes.asDeclared(element.asType())
+        val daoMethods = allMembers.filter {
+            it.hasAnyOf(Modifier.ABSTRACT) && it.kind == ElementKind.METHOD
+        }.filterNot {
+            // remove methods that belong to room
+            val containing = it.enclosingElement
+            MoreElements.isType(containing) &&
+                    TypeName.get(containing.asType()) == RoomTypeNames.ROOM_DB
+        }.map {
+            val executable = MoreElements.asExecutable(it)
+            // TODO when we add support for non Dao return types (e.g. database), this code needs
+            // to change
+            val daoType = MoreTypes.asTypeElement(executable.returnType)
+            val dao = DaoProcessor(context, daoType, declaredType, dbVerifier).process()
+            DaoMethod(executable, executable.simpleName.toString(), dao)
+        }
+        validateUniqueDaoClasses(element, daoMethods, entities)
+        validateUniqueIndices(element, entities)
+        val version = AnnotationMirrors.getAnnotationValue(dbAnnotation, "version")
+                .getAsInt(1)!!.toInt()
+        val exportSchema = AnnotationMirrors.getAnnotationValue(dbAnnotation, "exportSchema")
+                .getAsBoolean(true)
+
+        val hasForeignKeys = entities.any { it.foreignKeys.isNotEmpty() }
+
+        val database = Database(
+                version = version,
+                element = element,
+                type = MoreElements.asType(element).asType(),
+                entities = entities,
+                daoMethods = daoMethods,
+                exportSchema = exportSchema,
+                enableForeignKeys = hasForeignKeys)
+        return database
+    }
+
+    private fun validateForeignKeys(element: TypeElement, entities: List<Entity>) {
+        val byTableName = entities.associateBy { it.tableName }
+        entities.forEach { entity ->
+            entity.foreignKeys.forEach foreignKeyLoop@ { foreignKey ->
+                val parent = byTableName[foreignKey.parentTable]
+                if (parent == null) {
+                    context.logger.e(element, ProcessorErrors
+                            .foreignKeyMissingParentEntityInDatabase(foreignKey.parentTable,
+                                    entity.element.qualifiedName.toString()))
+                    return@foreignKeyLoop
+                }
+                val parentFields = foreignKey.parentColumns.map { columnName ->
+                    val parentField = parent.fields.find {
+                        it.columnName == columnName
+                    }
+                    if (parentField == null) {
+                        context.logger.e(entity.element,
+                                ProcessorErrors.foreignKeyParentColumnDoesNotExist(
+                                        parentEntity = parent.element.qualifiedName.toString(),
+                                        missingColumn = columnName,
+                                        allColumns = parent.fields.map { it.columnName }))
+                    }
+                    parentField
+                }.filterNotNull()
+                if (parentFields.size != foreignKey.parentColumns.size) {
+                    return@foreignKeyLoop
+                }
+                // ensure that it is indexed in the parent
+                if (!parent.isUnique(foreignKey.parentColumns)) {
+                    context.logger.e(parent.element, ProcessorErrors
+                            .foreignKeyMissingIndexInParent(
+                                    parentEntity = parent.element.qualifiedName.toString(),
+                                    childEntity = entity.element.qualifiedName.toString(),
+                                    parentColumns = foreignKey.parentColumns,
+                                    childColumns = foreignKey.childFields
+                                            .map { it.columnName }))
+                    return@foreignKeyLoop
+                }
+            }
+        }
+    }
+
+    private fun validateUniqueIndices(element: TypeElement, entities: List<Entity>) {
+        entities
+                .flatMap { entity ->
+                    // associate each index with its entity
+                    entity.indices.map { Pair(it.name, entity) }
+                }
+                .groupBy { it.first } // group by index name
+                .filter { it.value.size > 1 } // get the ones with duplicate names
+                .forEach {
+                    // do not report duplicates from the same entity
+                    if (it.value.distinctBy { it.second.typeName }.size > 1) {
+                        context.logger.e(element,
+                                ProcessorErrors.duplicateIndexInDatabase(it.key,
+                                        it.value.map { "${it.second.typeName} > ${it.first}" }))
+                    }
+                }
+    }
+
+    private fun validateUniqueDaoClasses(dbElement: TypeElement, daoMethods: List<DaoMethod>,
+                                         entities : List<Entity>) {
+        val entityTypeNames = entities.map { it.typeName }.toSet()
+        daoMethods.groupBy { it.dao.typeName }
+                .forEach {
+                    if (it.value.size > 1) {
+                        val error = ProcessorErrors.duplicateDao(it.key, it.value.map { it.name })
+                        it.value.forEach { daoMethod ->
+                            context.logger.e(daoMethod.element,
+                                    ProcessorErrors.DAO_METHOD_CONFLICTS_WITH_OTHERS)
+                        }
+                        // also report the full error for the database
+                        context.logger.e(dbElement, error)
+                    }
+                }
+        val check = fun(element : Element, dao : Dao,
+                        typeName : TypeName?) {
+            typeName?.let {
+                if (!entityTypeNames.contains(typeName)) {
+                    context.logger.e(element,
+                            ProcessorErrors.shortcutEntityIsNotInDatabase(
+                                    database = dbElement.qualifiedName.toString(),
+                                    dao = dao.typeName.toString(),
+                                    entity = typeName.toString()
+                            ))
+                }
+            }
+        }
+        daoMethods.forEach { daoMethod ->
+            daoMethod.dao.shortcutMethods.forEach { method ->
+                method.entities.forEach {
+                    check(method.element, daoMethod.dao, it.value.typeName)
+                }
+            }
+            daoMethod.dao.insertionMethods.forEach { method ->
+                method.entities.forEach {
+                    check(method.element, daoMethod.dao, it.value.typeName)
+                }
+            }
+        }
+    }
+
+    private fun validateUniqueTableNames(dbElement: TypeElement, entities: List<Entity>) {
+        entities
+                .groupBy {
+                    it.tableName.toLowerCase()
+                }.filter {
+            it.value.size > 1
+        }.forEach { byTableName ->
+            val error = ProcessorErrors.duplicateTableNames(byTableName.key,
+                    byTableName.value.map { it.typeName.toString() })
+            // report it for each of them and the database to make it easier
+            // for the developer
+            byTableName.value.forEach { entity ->
+                context.logger.e(entity.element, error)
+            }
+            context.logger.e(dbElement, error)
+        }
+    }
+
+    private fun processEntities(dbAnnotation: AnnotationMirror?, element: TypeElement):
+            List<Entity> {
+        if (!context.checker.check(dbAnnotation != null, element,
+                ProcessorErrors.DATABASE_MUST_BE_ANNOTATED_WITH_DATABASE)) {
+            return listOf()
+        }
+
+        val entityList = AnnotationMirrors.getAnnotationValue(dbAnnotation, "entities")
+        val listOfTypes = entityList.toListOfClassTypes()
+        context.checker.check(listOfTypes.isNotEmpty(), element,
+                ProcessorErrors.DATABASE_ANNOTATION_MUST_HAVE_LIST_OF_ENTITIES)
+        return listOfTypes.map {
+            EntityProcessor(context, MoreTypes.asTypeElement(it)).process()
+        }
+    }
+}
diff --git a/room/compiler/src/main/kotlin/android/arch/persistence/room/processor/DeletionMethodProcessor.kt b/room/compiler/src/main/kotlin/android/arch/persistence/room/processor/DeletionMethodProcessor.kt
new file mode 100644
index 0000000..4812306
--- /dev/null
+++ b/room/compiler/src/main/kotlin/android/arch/persistence/room/processor/DeletionMethodProcessor.kt
@@ -0,0 +1,54 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package android.arch.persistence.room.processor
+
+import android.arch.persistence.room.Delete
+import android.arch.persistence.room.ext.typeName
+import android.arch.persistence.room.vo.DeletionMethod
+import com.squareup.javapoet.TypeName
+import javax.lang.model.element.ExecutableElement
+import javax.lang.model.type.DeclaredType
+
+class DeletionMethodProcessor(baseContext: Context,
+                              val containing: DeclaredType,
+                              val executableElement: ExecutableElement) {
+    val context = baseContext.fork(executableElement)
+
+    fun process(): DeletionMethod {
+        val delegate = ShortcutMethodProcessor(context, containing, executableElement)
+        delegate.extractAnnotation(Delete::class, ProcessorErrors.MISSING_DELETE_ANNOTATION)
+
+        val returnTypeName = delegate.extractReturnType().typeName()
+        context.checker.check(
+                returnTypeName == TypeName.VOID || returnTypeName == TypeName.INT,
+                executableElement,
+                ProcessorErrors.DELETION_METHODS_MUST_RETURN_VOID_OR_INT
+        )
+
+        val (entities, params) = delegate.extractParams(
+                missingParamError = ProcessorErrors
+                        .DELETION_MISSING_PARAMS
+        )
+
+        return DeletionMethod(
+                element = delegate.executableElement,
+                name = delegate.executableElement.simpleName.toString(),
+                entities = entities,
+                returnCount = returnTypeName == TypeName.INT,
+                parameters = params
+        )
+    }
+}
diff --git a/room/compiler/src/main/kotlin/android/arch/persistence/room/processor/EntityProcessor.kt b/room/compiler/src/main/kotlin/android/arch/persistence/room/processor/EntityProcessor.kt
new file mode 100644
index 0000000..4f4a7f4
--- /dev/null
+++ b/room/compiler/src/main/kotlin/android/arch/persistence/room/processor/EntityProcessor.kt
@@ -0,0 +1,543 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.arch.persistence.room.processor
+
+import android.arch.persistence.room.ext.getAsBoolean
+import android.arch.persistence.room.ext.getAsInt
+import android.arch.persistence.room.ext.getAsString
+import android.arch.persistence.room.ext.getAsStringList
+import android.arch.persistence.room.ext.toType
+import android.arch.persistence.room.parser.SQLTypeAffinity
+import android.arch.persistence.room.processor.ProcessorErrors.INDEX_COLUMNS_CANNOT_BE_EMPTY
+import android.arch.persistence.room.processor.ProcessorErrors.RELATION_IN_ENTITY
+import android.arch.persistence.room.processor.cache.Cache
+import android.arch.persistence.room.vo.EmbeddedField
+import android.arch.persistence.room.vo.Entity
+import android.arch.persistence.room.vo.Field
+import android.arch.persistence.room.vo.ForeignKey
+import android.arch.persistence.room.vo.ForeignKeyAction
+import android.arch.persistence.room.vo.Index
+import android.arch.persistence.room.vo.Pojo
+import android.arch.persistence.room.vo.PrimaryKey
+import android.arch.persistence.room.vo.Warning
+import com.google.auto.common.AnnotationMirrors
+import com.google.auto.common.AnnotationMirrors.getAnnotationValue
+import com.google.auto.common.MoreElements
+import com.google.auto.common.MoreTypes
+import javax.lang.model.element.AnnotationMirror
+import javax.lang.model.element.AnnotationValue
+import javax.lang.model.element.TypeElement
+import javax.lang.model.type.TypeKind
+import javax.lang.model.type.TypeMirror
+import javax.lang.model.util.SimpleAnnotationValueVisitor6
+
+class EntityProcessor(baseContext: Context, val element: TypeElement) {
+    val context = baseContext.fork(element)
+
+    fun process(): Entity {
+        return context.cache.entities.get(Cache.EntityKey(element), {
+            doProcess()
+        })
+    }
+    private fun doProcess() : Entity {
+        context.checker.hasAnnotation(element, android.arch.persistence.room.Entity::class,
+                ProcessorErrors.ENTITY_MUST_BE_ANNOTATED_WITH_ENTITY)
+        val pojo = PojoProcessor(
+                baseContext = context,
+                element = element,
+                bindingScope = FieldProcessor.BindingScope.TWO_WAY,
+                parent = null).process()
+        context.checker.check(pojo.relations.isEmpty(), element, RELATION_IN_ENTITY)
+        val annotation = MoreElements.getAnnotationMirror(element,
+                android.arch.persistence.room.Entity::class.java).orNull()
+        val tableName: String
+        val entityIndices: List<IndexInput>
+        val foreignKeyInputs: List<ForeignKeyInput>
+        val inheritSuperIndices: Boolean
+        if (annotation != null) {
+            tableName = extractTableName(element, annotation)
+            entityIndices = extractIndices(annotation, tableName)
+            inheritSuperIndices = AnnotationMirrors
+                    .getAnnotationValue(annotation, "inheritSuperIndices").getAsBoolean(false)
+            foreignKeyInputs = extractForeignKeys(annotation)
+        } else {
+            tableName = element.simpleName.toString()
+            foreignKeyInputs = emptyList()
+            entityIndices = emptyList()
+            inheritSuperIndices = false
+        }
+        context.checker.notBlank(tableName, element,
+                ProcessorErrors.ENTITY_TABLE_NAME_CANNOT_BE_EMPTY)
+
+        val fieldIndices = pojo.fields
+                .filter { it.indexed }
+                .map {
+                    if (it.parent != null) {
+                        it.indexed = false
+                        context.logger.w(Warning.INDEX_FROM_EMBEDDED_FIELD_IS_DROPPED, it.element,
+                                ProcessorErrors.droppedEmbeddedFieldIndex(
+                                        it.getPath(), element.qualifiedName.toString()))
+                        null
+                    } else if (it.element.enclosingElement != element && !inheritSuperIndices) {
+                        it.indexed = false
+                        context.logger.w(Warning.INDEX_FROM_PARENT_FIELD_IS_DROPPED,
+                                ProcessorErrors.droppedSuperClassFieldIndex(
+                                        it.columnName, element.toString(),
+                                        it.element.enclosingElement.toString()
+                                ))
+                        null
+                    } else {
+                        IndexInput(
+                                name = createIndexName(listOf(it.columnName), tableName),
+                                unique = false,
+                                columnNames = listOf(it.columnName)
+                        )
+                    }
+                }.filterNotNull()
+        val superIndices = loadSuperIndices(element.superclass, tableName, inheritSuperIndices)
+        val indexInputs = entityIndices + fieldIndices + superIndices
+        val indices = validateAndCreateIndices(indexInputs, pojo)
+
+        val primaryKey = findPrimaryKey(pojo.fields, pojo.embeddedFields)
+        val affinity = primaryKey.fields.firstOrNull()?.affinity ?: SQLTypeAffinity.TEXT
+        context.checker.check(
+                !primaryKey.autoGenerateId || affinity == SQLTypeAffinity.INTEGER,
+                primaryKey.fields.firstOrNull()?.element ?: element,
+                ProcessorErrors.AUTO_INCREMENTED_PRIMARY_KEY_IS_NOT_INT
+        )
+
+        val entityForeignKeys = validateAndCreateForeignKeyReferences(foreignKeyInputs, pojo)
+        checkIndicesForForeignKeys(entityForeignKeys, primaryKey, indices)
+
+        val entity = Entity(element = element,
+                tableName = tableName,
+                type = pojo.type,
+                fields = pojo.fields,
+                embeddedFields = pojo.embeddedFields,
+                indices = indices,
+                primaryKey = primaryKey,
+                foreignKeys = entityForeignKeys,
+                constructor = pojo.constructor)
+
+        return entity
+    }
+
+    private fun checkIndicesForForeignKeys(entityForeignKeys: List<ForeignKey>,
+                                           primaryKey: PrimaryKey,
+                                           indices: List<Index>) {
+        fun covers(columnNames: List<String>, fields : List<Field>) : Boolean =
+            fields.size >= columnNames.size && columnNames.withIndex().all {
+                fields[it.index].columnName == it.value
+            }
+
+        entityForeignKeys.forEach { fKey ->
+            val columnNames = fKey.childFields.map { it.columnName }
+            val exists = covers(columnNames, primaryKey.fields) || indices.any { index ->
+                covers(columnNames, index.fields)
+            }
+            if (!exists) {
+                if (columnNames.size == 1) {
+                    context.logger.w(Warning.MISSING_INDEX_ON_FOREIGN_KEY_CHILD, element,
+                            ProcessorErrors.foreignKeyMissingIndexInChildColumn(columnNames[0]))
+                } else {
+                    context.logger.w(Warning.MISSING_INDEX_ON_FOREIGN_KEY_CHILD, element,
+                            ProcessorErrors.foreignKeyMissingIndexInChildColumns(columnNames))
+                }
+            }
+        }
+    }
+
+    /**
+     * Does a validation on foreign keys except the parent table's columns.
+     */
+    private fun validateAndCreateForeignKeyReferences(foreignKeyInputs: List<ForeignKeyInput>,
+                                                      pojo: Pojo): List<ForeignKey> {
+        return foreignKeyInputs.map {
+            if (it.onUpdate == null) {
+                context.logger.e(element, ProcessorErrors.INVALID_FOREIGN_KEY_ACTION)
+                return@map null
+            }
+            if (it.onDelete == null) {
+                context.logger.e(element, ProcessorErrors.INVALID_FOREIGN_KEY_ACTION)
+                return@map null
+            }
+            if (it.childColumns.isEmpty()) {
+                context.logger.e(element, ProcessorErrors.FOREIGN_KEY_EMPTY_CHILD_COLUMN_LIST)
+                return@map null
+            }
+            if (it.parentColumns.isEmpty()) {
+                context.logger.e(element, ProcessorErrors.FOREIGN_KEY_EMPTY_PARENT_COLUMN_LIST)
+                return@map null
+            }
+            if (it.childColumns.size != it.parentColumns.size) {
+                context.logger.e(element, ProcessorErrors.foreignKeyColumnNumberMismatch(
+                        it.childColumns, it.parentColumns
+                ))
+                return@map null
+            }
+            val parentElement = try {
+                MoreTypes.asElement(it.parent) as TypeElement
+            } catch (noClass: IllegalArgumentException) {
+                context.logger.e(element, ProcessorErrors.FOREIGN_KEY_CANNOT_FIND_PARENT)
+                return@map null
+            }
+            val parentAnnotation = MoreElements.getAnnotationMirror(parentElement,
+                    android.arch.persistence.room.Entity::class.java).orNull()
+            if (parentAnnotation == null) {
+                context.logger.e(element,
+                        ProcessorErrors.foreignKeyNotAnEntity(parentElement.toString()))
+                return@map null
+            }
+            val tableName = extractTableName(parentElement, parentAnnotation)
+            val fields = it.childColumns.map { columnName ->
+                val field = pojo.fields.find { it.columnName == columnName }
+                if (field == null) {
+                    context.logger.e(pojo.element,
+                            ProcessorErrors.foreignKeyChildColumnDoesNotExist(columnName,
+                                    pojo.fields.map { it.columnName }))
+                }
+                field
+            }.filterNotNull()
+            if (fields.size != it.childColumns.size) {
+                return@map null
+            }
+            ForeignKey(
+                    parentTable = tableName,
+                    childFields = fields,
+                    parentColumns = it.parentColumns,
+                    onDelete = it.onDelete,
+                    onUpdate = it.onUpdate,
+                    deferred = it.deferred
+            )
+        }.filterNotNull()
+    }
+
+    private fun findPrimaryKey(fields: List<Field>, embeddedFields: List<EmbeddedField>)
+            : PrimaryKey {
+        val candidates = collectPrimaryKeysFromEntityAnnotations(element, fields) +
+                collectPrimaryKeysFromPrimaryKeyAnnotations(fields) +
+                collectPrimaryKeysFromEmbeddedFields(embeddedFields)
+
+        context.checker.check(candidates.isNotEmpty(), element, ProcessorErrors.MISSING_PRIMARY_KEY)
+        if (candidates.size == 1) {
+            // easy :)
+            return candidates.first()
+        }
+
+        return choosePrimaryKey(candidates, element)
+    }
+
+    /**
+     * Check fields for @PrimaryKey.
+     */
+    private fun collectPrimaryKeysFromPrimaryKeyAnnotations(fields: List<Field>): List<PrimaryKey> {
+        return fields.map { field ->
+            MoreElements.getAnnotationMirror(field.element,
+                    android.arch.persistence.room.PrimaryKey::class.java).orNull()?.let {
+                if (field.parent != null) {
+                    // the field in the entity that contains this error.
+                    val grandParentField = field.parent.mRootParent.field.element
+                    // bound for entity.
+                    context.fork(grandParentField).logger.w(
+                            Warning.PRIMARY_KEY_FROM_EMBEDDED_IS_DROPPED,
+                            grandParentField,
+                            ProcessorErrors.embeddedPrimaryKeyIsDropped(
+                                    element.qualifiedName.toString(), field.name))
+                    null
+                } else {
+                    PrimaryKey(declaredIn = field.element.enclosingElement,
+                            fields = listOf(field),
+                            autoGenerateId = AnnotationMirrors
+                                    .getAnnotationValue(it, "autoGenerate")
+                                    .getAsBoolean(false))
+                }
+            }
+        }.filterNotNull()
+    }
+
+    /**
+     * Check classes for @Entity(primaryKeys = ?).
+     */
+    private fun collectPrimaryKeysFromEntityAnnotations(typeElement: TypeElement,
+                                                        availableFields: List<Field>)
+            : List<PrimaryKey> {
+        val myPkeys = MoreElements.getAnnotationMirror(typeElement,
+                android.arch.persistence.room.Entity::class.java).orNull()?.let {
+            val primaryKeyColumns = AnnotationMirrors.getAnnotationValue(it, "primaryKeys")
+                    .getAsStringList()
+            if (primaryKeyColumns.isEmpty()) {
+                emptyList<PrimaryKey>()
+            } else {
+                val fields = primaryKeyColumns.map { pKeyColumnName ->
+                    val field = availableFields.firstOrNull { it.columnName == pKeyColumnName }
+                    context.checker.check(field != null, typeElement,
+                            ProcessorErrors.primaryKeyColumnDoesNotExist(pKeyColumnName,
+                                    availableFields.map { it.columnName }))
+                    field
+                }.filterNotNull()
+                listOf(PrimaryKey(declaredIn = typeElement,
+                        fields = fields,
+                        autoGenerateId = false))
+            }
+        } ?: emptyList<PrimaryKey>()
+        // checks supers.
+        val mySuper = typeElement.superclass
+        val superPKeys = if (mySuper != null && mySuper.kind != TypeKind.NONE) {
+            // my super cannot see my fields so remove them.
+            val remainingFields = availableFields.filterNot {
+                it.element.enclosingElement == typeElement
+            }
+            collectPrimaryKeysFromEntityAnnotations(
+                    MoreTypes.asTypeElement(mySuper), remainingFields)
+        } else {
+            emptyList()
+        }
+        return superPKeys + myPkeys
+    }
+
+    private fun collectPrimaryKeysFromEmbeddedFields(embeddedFields: List<EmbeddedField>)
+            : List<PrimaryKey> {
+        return embeddedFields.map { embeddedField ->
+            MoreElements.getAnnotationMirror(embeddedField.field.element,
+                    android.arch.persistence.room.PrimaryKey::class.java).orNull()?.let {
+                val autoGenerate = AnnotationMirrors
+                        .getAnnotationValue(it, "autoGenerate").getAsBoolean(false)
+                context.checker.check(!autoGenerate || embeddedField.pojo.fields.size == 1,
+                        embeddedField.field.element,
+                        ProcessorErrors.AUTO_INCREMENT_EMBEDDED_HAS_MULTIPLE_FIELDS)
+                PrimaryKey(declaredIn = embeddedField.field.element.enclosingElement,
+                        fields = embeddedField.pojo.fields,
+                        autoGenerateId = autoGenerate)
+            }
+        }.filterNotNull()
+    }
+
+    // start from my element and check if anywhere in the list we can find the only well defined
+    // pkey, if so, use it.
+    private fun choosePrimaryKey(candidates: List<PrimaryKey>, typeElement: TypeElement)
+            : PrimaryKey {
+        // If 1 of these primary keys is declared in this class, then it is the winner. Just print
+        //    a note for the others.
+        // If 0 is declared, check the parent.
+        // If more than 1 primary key is declared in this class, it is an error.
+        val myPKeys = candidates.filter { candidate ->
+            candidate.declaredIn == typeElement
+        }
+        return if (myPKeys.size == 1) {
+            // just note, this is not worth an error or warning
+            (candidates - myPKeys).forEach {
+                context.logger.d(element,
+                        "${it.toHumanReadableString()} is" +
+                                " overridden by ${myPKeys.first().toHumanReadableString()}")
+            }
+            myPKeys.first()
+        } else if (myPKeys.isEmpty()) {
+            // i have not declared anything, delegate to super
+            val mySuper = typeElement.superclass
+            if (mySuper != null && mySuper.kind != TypeKind.NONE) {
+                return choosePrimaryKey(candidates, MoreTypes.asTypeElement(mySuper))
+            }
+            PrimaryKey.MISSING
+        } else {
+            context.logger.e(element, ProcessorErrors.multiplePrimaryKeyAnnotations(
+                    myPKeys.map(PrimaryKey::toHumanReadableString)))
+            PrimaryKey.MISSING
+        }
+    }
+
+    private fun validateAndCreateIndices(inputs: List<IndexInput>, pojo: Pojo): List<Index> {
+        // check for columns
+        val indices = inputs.map { input ->
+            context.checker.check(input.columnNames.isNotEmpty(), element,
+                    INDEX_COLUMNS_CANNOT_BE_EMPTY)
+            val fields = input.columnNames.map { columnName ->
+                val field = pojo.fields.firstOrNull {
+                    it.columnName == columnName
+                }
+                context.checker.check(field != null, element,
+                        ProcessorErrors.indexColumnDoesNotExist(
+                                columnName, pojo.fields.map { it.columnName }
+                        ))
+                field
+            }.filterNotNull()
+            if (fields.isEmpty()) {
+                null
+            } else {
+                Index(name = input.name, unique = input.unique, fields = fields)
+            }
+        }.filterNotNull()
+
+        // check for duplicate indices
+        indices
+                .groupBy { it.name }
+                .filter { it.value.size > 1 }
+                .forEach {
+                    context.logger.e(element, ProcessorErrors.duplicateIndexInEntity(it.key))
+                }
+
+        // see if any embedded field is an entity with indices, if so, report a warning
+        pojo.embeddedFields.forEach { embedded ->
+            val embeddedElement = embedded.pojo.element
+            val subEntityAnnotation = MoreElements.getAnnotationMirror(embeddedElement,
+                    android.arch.persistence.room.Entity::class.java).orNull()
+            subEntityAnnotation?.let {
+                val subIndices = extractIndices(subEntityAnnotation, "")
+                if (subIndices.isNotEmpty()) {
+                    context.logger.w(Warning.INDEX_FROM_EMBEDDED_ENTITY_IS_DROPPED,
+                            embedded.field.element, ProcessorErrors.droppedEmbeddedIndex(
+                            entityName = embedded.pojo.typeName.toString(),
+                            fieldPath = embedded.field.getPath(),
+                            grandParent = element.qualifiedName.toString()))
+                }
+            }
+        }
+        return indices
+    }
+
+    // check if parent is an Entity, if so, report its annotation indices
+    private fun loadSuperIndices(typeMirror: TypeMirror?, tableName: String, inherit: Boolean)
+            : List<IndexInput> {
+        if (typeMirror == null || typeMirror.kind == TypeKind.NONE) {
+            return emptyList()
+        }
+        val parentElement = MoreTypes.asTypeElement(typeMirror)
+        val myIndices = MoreElements.getAnnotationMirror(parentElement,
+                android.arch.persistence.room.Entity::class.java).orNull()?.let { annotation ->
+            val indices = extractIndices(annotation, tableName = "super")
+            if (indices.isEmpty()) {
+                emptyList()
+            } else if (inherit) {
+                // rename them
+                indices.map {
+                    IndexInput(
+                            name = createIndexName(it.columnNames, tableName),
+                            unique = it.unique,
+                            columnNames = it.columnNames)
+                }
+            } else {
+                context.logger.w(Warning.INDEX_FROM_PARENT_IS_DROPPED,
+                        parentElement,
+                        ProcessorErrors.droppedSuperClassIndex(
+                                childEntity = element.qualifiedName.toString(),
+                                superEntity = parentElement.qualifiedName.toString()))
+                emptyList()
+            }
+        } ?: emptyList()
+        return myIndices + loadSuperIndices(parentElement.superclass, tableName, inherit)
+    }
+
+    companion object {
+        private fun extractTableName(element: TypeElement, annotation: AnnotationMirror)
+                : String {
+            val annotationValue = AnnotationMirrors
+                    .getAnnotationValue(annotation, "tableName").value.toString()
+            return if (annotationValue == "") {
+                element.simpleName.toString()
+            } else {
+                annotationValue
+            }
+        }
+
+        private fun extractIndices(annotation: AnnotationMirror, tableName: String)
+                : List<IndexInput> {
+            val arrayOfIndexAnnotations = AnnotationMirrors.getAnnotationValue(annotation,
+                    "indices")
+            return INDEX_LIST_VISITOR.visit(arrayOfIndexAnnotations, tableName)
+        }
+
+        private val INDEX_LIST_VISITOR = object
+            : SimpleAnnotationValueVisitor6<List<IndexInput>, String>() {
+            override fun visitArray(values: MutableList<out AnnotationValue>?, tableName: String)
+                    : List<IndexInput> {
+                return values?.map {
+                    INDEX_VISITOR.visit(it, tableName)
+                }?.filterNotNull() ?: emptyList<IndexInput>()
+            }
+        }
+
+        private val INDEX_VISITOR = object : SimpleAnnotationValueVisitor6<IndexInput?, String>() {
+            override fun visitAnnotation(a: AnnotationMirror?, tableName: String): IndexInput? {
+                val fieldInput = getAnnotationValue(a, "value").getAsStringList()
+                val unique = getAnnotationValue(a, "unique").getAsBoolean(false)
+                val nameValue = getAnnotationValue(a, "name")
+                        .getAsString("")
+                val name = if (nameValue == null || nameValue == "") {
+                    createIndexName(fieldInput, tableName)
+                } else {
+                    nameValue
+                }
+                return IndexInput(name, unique, fieldInput)
+            }
+        }
+
+        private fun createIndexName(columnNames: List<String>, tableName: String): String {
+            return "index_" + tableName + "_" + columnNames.joinToString("_")
+        }
+
+        private fun extractForeignKeys(annotation: AnnotationMirror): List<ForeignKeyInput> {
+            val arrayOfForeignKeyAnnotations = getAnnotationValue(annotation, "foreignKeys")
+            return FOREIGN_KEY_LIST_VISITOR.visit(arrayOfForeignKeyAnnotations)
+        }
+
+        private val FOREIGN_KEY_LIST_VISITOR = object
+            : SimpleAnnotationValueVisitor6<List<ForeignKeyInput>, Void?>() {
+            override fun visitArray(values: MutableList<out AnnotationValue>?, void: Void?)
+                    : List<ForeignKeyInput> {
+                return values?.map {
+                    FOREIGN_KEY_VISITOR.visit(it)
+                }?.filterNotNull() ?: emptyList<ForeignKeyInput>()
+            }
+        }
+
+        private val FOREIGN_KEY_VISITOR = object : SimpleAnnotationValueVisitor6<ForeignKeyInput?,
+                Void?>() {
+            override fun visitAnnotation(a: AnnotationMirror?, void: Void?): ForeignKeyInput? {
+                val entityClass = try {
+                    getAnnotationValue(a, "entity").toType()
+                } catch (notPresent: TypeNotPresentException) {
+                    return null
+                }
+                val parentColumns = getAnnotationValue(a, "parentColumns").getAsStringList()
+                val childColumns = getAnnotationValue(a, "childColumns").getAsStringList()
+                val onDeleteInput = getAnnotationValue(a, "onDelete").getAsInt()
+                val onUpdateInput = getAnnotationValue(a, "onUpdate").getAsInt()
+                val deferred = getAnnotationValue(a, "deferred").getAsBoolean(true)
+                val onDelete = ForeignKeyAction.fromAnnotationValue(onDeleteInput)
+                val onUpdate = ForeignKeyAction.fromAnnotationValue(onUpdateInput)
+                return ForeignKeyInput(
+                        parent = entityClass,
+                        parentColumns = parentColumns,
+                        childColumns = childColumns,
+                        onDelete = onDelete,
+                        onUpdate = onUpdate,
+                        deferred = deferred)
+            }
+        }
+    }
+
+    /**
+     * processed Index annotation output
+     */
+    data class IndexInput(val name: String, val unique: Boolean, val columnNames: List<String>)
+
+    /**
+     * ForeignKey, before it is processed in the context of a database.
+     */
+    data class ForeignKeyInput(val parent : TypeMirror, val parentColumns : List<String>,
+                     val childColumns : List<String>, val onDelete : ForeignKeyAction?,
+                     val onUpdate : ForeignKeyAction?, val deferred : Boolean)
+}
diff --git a/room/compiler/src/main/kotlin/android/arch/persistence/room/processor/FieldProcessor.kt b/room/compiler/src/main/kotlin/android/arch/persistence/room/processor/FieldProcessor.kt
new file mode 100644
index 0000000..2d11588
--- /dev/null
+++ b/room/compiler/src/main/kotlin/android/arch/persistence/room/processor/FieldProcessor.kt
@@ -0,0 +1,122 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.arch.persistence.room.processor
+
+import android.arch.persistence.room.ColumnInfo
+import android.arch.persistence.room.ext.getAsBoolean
+import android.arch.persistence.room.ext.getAsInt
+import android.arch.persistence.room.ext.getAsString
+import android.arch.persistence.room.parser.SQLTypeAffinity
+import android.arch.persistence.room.vo.EmbeddedField
+import android.arch.persistence.room.vo.Field
+import com.google.auto.common.AnnotationMirrors
+import com.google.auto.common.MoreElements
+import com.squareup.javapoet.TypeName
+import javax.lang.model.element.Element
+import javax.lang.model.type.DeclaredType
+
+class FieldProcessor(baseContext: Context, val containing: DeclaredType, val element: Element,
+                     val bindingScope: BindingScope,
+                     // pass only if this is processed as a child of Embedded field
+                     val fieldParent: EmbeddedField?) {
+    val context = baseContext.fork(element)
+    fun process(): Field {
+        val member = context.processingEnv.typeUtils.asMemberOf(containing, element)
+        val type = TypeName.get(member)
+        val columnInfoAnnotation = MoreElements.getAnnotationMirror(element,
+                ColumnInfo::class.java)
+        val name = element.simpleName.toString()
+        val columnName: String
+        val affinity : SQLTypeAffinity?
+        val fieldPrefix = fieldParent?.prefix ?: ""
+        val indexed : Boolean
+        if (columnInfoAnnotation.isPresent) {
+            val nameInAnnotation = AnnotationMirrors
+                    .getAnnotationValue(columnInfoAnnotation.get(), "name")
+                    .getAsString(ColumnInfo.INHERIT_FIELD_NAME)
+            columnName = fieldPrefix + if (nameInAnnotation == ColumnInfo.INHERIT_FIELD_NAME) {
+                name
+            } else {
+                nameInAnnotation
+            }
+
+            affinity = try {
+                val userDefinedAffinity = AnnotationMirrors
+                        .getAnnotationValue(columnInfoAnnotation.get(), "typeAffinity")
+                        .getAsInt(ColumnInfo.UNDEFINED)!!
+                SQLTypeAffinity.fromAnnotationValue(userDefinedAffinity)
+            } catch (ex : NumberFormatException) {
+                null
+            }
+
+            indexed = AnnotationMirrors
+                    .getAnnotationValue(columnInfoAnnotation.get(), "index")
+                    .getAsBoolean(false)
+
+        } else {
+            columnName = fieldPrefix + name
+            affinity = null
+            indexed = false
+        }
+        context.checker.notBlank(columnName, element,
+                ProcessorErrors.COLUMN_NAME_CANNOT_BE_EMPTY)
+        context.checker.notUnbound(type, element,
+                ProcessorErrors.CANNOT_USE_UNBOUND_GENERICS_IN_ENTITY_FIELDS)
+
+        val field = Field(name = name,
+                type = member,
+                element = element,
+                columnName = columnName,
+                affinity = affinity,
+                parent = fieldParent,
+                indexed = indexed)
+
+        when (bindingScope) {
+            BindingScope.TWO_WAY -> {
+                val adapter = context.typeAdapterStore.findColumnTypeAdapter(field.type,
+                        field.affinity)
+                field.statementBinder = adapter
+                field.cursorValueReader = adapter
+                field.affinity = adapter?.typeAffinity ?: field.affinity
+                context.checker.check(adapter != null, field.element,
+                        ProcessorErrors.CANNOT_FIND_COLUMN_TYPE_ADAPTER)
+            }
+            BindingScope.BIND_TO_STMT -> {
+                field.statementBinder = context.typeAdapterStore
+                        .findStatementValueBinder(field.type, field.affinity)
+                context.checker.check(field.statementBinder != null, field.element,
+                        ProcessorErrors.CANNOT_FIND_STMT_BINDER)
+            }
+            BindingScope.READ_FROM_CURSOR -> {
+                field.cursorValueReader = context.typeAdapterStore
+                        .findCursorValueReader(field.type, field.affinity)
+                context.checker.check(field.cursorValueReader != null, field.element,
+                        ProcessorErrors.CANNOT_FIND_CURSOR_READER)
+            }
+        }
+        return field
+    }
+
+    /**
+     * Defines what we need to assign
+     */
+    enum class BindingScope {
+        TWO_WAY, // both bind and read.
+        BIND_TO_STMT, // just value to statement
+        READ_FROM_CURSOR // just cursor to value
+    }
+}
diff --git a/room/compiler/src/main/kotlin/android/arch/persistence/room/processor/InsertionMethodProcessor.kt b/room/compiler/src/main/kotlin/android/arch/persistence/room/processor/InsertionMethodProcessor.kt
new file mode 100644
index 0000000..dba4a87
--- /dev/null
+++ b/room/compiler/src/main/kotlin/android/arch/persistence/room/processor/InsertionMethodProcessor.kt
@@ -0,0 +1,151 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+@file:Suppress("PLATFORM_CLASS_MAPPED_TO_KOTLIN")
+
+package android.arch.persistence.room.processor
+
+import android.support.annotation.VisibleForTesting
+import android.arch.persistence.room.Insert
+import android.arch.persistence.room.OnConflictStrategy.IGNORE
+import android.arch.persistence.room.OnConflictStrategy.REPLACE
+import android.arch.persistence.room.vo.InsertionMethod
+import android.arch.persistence.room.vo.InsertionMethod.Type
+import android.arch.persistence.room.vo.ShortcutQueryParameter
+import com.google.auto.common.MoreTypes
+import com.squareup.javapoet.TypeName
+import javax.lang.model.element.ExecutableElement
+import javax.lang.model.type.DeclaredType
+import javax.lang.model.type.TypeKind
+import javax.lang.model.type.TypeKind.LONG
+import javax.lang.model.type.TypeKind.VOID
+import javax.lang.model.type.TypeMirror
+
+class InsertionMethodProcessor(baseContext: Context,
+                               val containing: DeclaredType,
+                               val executableElement: ExecutableElement) {
+    val context = baseContext.fork(executableElement)
+    fun process(): InsertionMethod {
+        val delegate = ShortcutMethodProcessor(context, containing, executableElement)
+        val annotation = delegate.extractAnnotation(Insert::class,
+                ProcessorErrors.MISSING_INSERT_ANNOTATION)
+
+        val onConflict = OnConflictProcessor.extractFrom(annotation)
+        context.checker.check(onConflict <= IGNORE && onConflict >= REPLACE,
+                executableElement, ProcessorErrors.INVALID_ON_CONFLICT_VALUE)
+
+        val returnType = delegate.extractReturnType()
+        val returnTypeName = TypeName.get(returnType)
+        context.checker.notUnbound(returnTypeName, executableElement,
+                ProcessorErrors.CANNOT_USE_UNBOUND_GENERICS_IN_INSERTION_METHODS)
+
+        val (entities, params) = delegate.extractParams(
+                missingParamError = ProcessorErrors
+                        .INSERTION_DOES_NOT_HAVE_ANY_PARAMETERS_TO_INSERT
+        )
+
+        // TODO we can support more types
+        var insertionType = getInsertionType(returnType)
+        context.checker.check(insertionType != null, executableElement,
+                ProcessorErrors.INVALID_INSERTION_METHOD_RETURN_TYPE)
+
+        if (insertionType != null) {
+            val acceptable = acceptableTypes(params)
+            if (insertionType !in acceptable) {
+                context.logger.e(executableElement,
+                        ProcessorErrors.insertionMethodReturnTypeMismatch(
+                                insertionType.returnTypeName,
+                                acceptable.map { it.returnTypeName }))
+                // clear it, no reason to generate code for it.
+                insertionType = null
+            }
+        }
+        return InsertionMethod(
+                element = executableElement,
+                name = executableElement.simpleName.toString(),
+                returnType = returnType,
+                entities = entities,
+                parameters = params,
+                onConflict = onConflict,
+                insertionType = insertionType
+        )
+    }
+
+    @Suppress("PLATFORM_CLASS_MAPPED_TO_KOTLIN")
+    private fun getInsertionType(returnType: TypeMirror): InsertionMethod.Type? {
+        // TODO we need to support more types here.
+        fun isLongPrimitiveType(typeMirror: TypeMirror) = typeMirror.kind == LONG
+
+        fun isLongBoxType(typeMirror: TypeMirror) =
+                MoreTypes.isType(typeMirror) &&
+                        MoreTypes.isTypeOf(java.lang.Long::class.java, typeMirror)
+
+        fun isLongType(typeMirror: TypeMirror) =
+                isLongPrimitiveType(typeMirror) || isLongBoxType(typeMirror)
+
+        return if (returnType.kind == VOID) {
+            Type.INSERT_VOID
+        } else if (returnType.kind == TypeKind.ARRAY) {
+            val arrayType = MoreTypes.asArray(returnType)
+            val param = arrayType.componentType
+            if (isLongPrimitiveType(param)) {
+                Type.INSERT_ID_ARRAY
+            } else if (isLongBoxType(param)) {
+                Type.INSERT_ID_ARRAY_BOX
+            } else {
+                null
+            }
+        } else if (MoreTypes.isType(returnType)
+                && MoreTypes.isTypeOf(List::class.java, returnType)) {
+            val declared = MoreTypes.asDeclared(returnType)
+            val param = declared.typeArguments.first()
+            if (isLongBoxType(param)) {
+                Type.INSERT_ID_LIST
+            } else {
+                null
+            }
+        } else if (isLongType(returnType)) {
+            Type.INSERT_SINGLE_ID
+        } else {
+            null
+        }
+    }
+
+    companion object {
+        @VisibleForTesting
+        val VOID_SET by lazy { setOf(Type.INSERT_VOID) }
+        @VisibleForTesting
+        val SINGLE_ITEM_SET by lazy { setOf(Type.INSERT_VOID, Type.INSERT_SINGLE_ID) }
+        @VisibleForTesting
+        val MULTIPLE_ITEM_SET by lazy {
+            setOf(Type.INSERT_VOID, Type.INSERT_ID_ARRAY, Type.INSERT_ID_ARRAY_BOX,
+                    Type.INSERT_ID_LIST)
+        }
+        fun acceptableTypes(params : List<ShortcutQueryParameter>) : Set<InsertionMethod.Type> {
+            if (params.isEmpty()) {
+                return VOID_SET
+            }
+            if (params.size > 1) {
+                return VOID_SET
+            }
+            if (params.first().isMultiple) {
+                return MULTIPLE_ITEM_SET
+            } else {
+                return SINGLE_ITEM_SET
+            }
+        }
+    }
+}
diff --git a/room/compiler/src/main/kotlin/android/arch/persistence/room/processor/OnConflictProcessor.kt b/room/compiler/src/main/kotlin/android/arch/persistence/room/processor/OnConflictProcessor.kt
new file mode 100644
index 0000000..ac5ed5b
--- /dev/null
+++ b/room/compiler/src/main/kotlin/android/arch/persistence/room/processor/OnConflictProcessor.kt
@@ -0,0 +1,55 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.arch.persistence.room.processor
+
+import android.arch.persistence.room.OnConflictStrategy
+import com.google.auto.common.AnnotationMirrors
+import javax.lang.model.element.AnnotationMirror
+
+/**
+ * Processes on conflict fields in annotations
+ */
+object OnConflictProcessor {
+    val INVALID_ON_CONFLICT = -1
+
+    @OnConflictStrategy
+    fun extractFrom(annotation: AnnotationMirror?, fieldName: String = "onConflict"): Int {
+        return if (annotation == null) {
+            INVALID_ON_CONFLICT
+        } else {
+            try {
+                val onConflictValue = AnnotationMirrors
+                        .getAnnotationValue(annotation, fieldName)
+                        .value
+                onConflictValue.toString().toInt()
+            } catch (ex: NumberFormatException) {
+                INVALID_ON_CONFLICT
+            }
+        }
+    }
+
+    fun onConflictText(@OnConflictStrategy onConflict: Int): String {
+        return when (onConflict) {
+            OnConflictStrategy.REPLACE -> "REPLACE"
+            OnConflictStrategy.ABORT -> "ABORT"
+            OnConflictStrategy.FAIL -> "FAIL"
+            OnConflictStrategy.IGNORE -> "IGNORE"
+            OnConflictStrategy.ROLLBACK -> "ROLLBACK"
+            else -> "BAD_CONFLICT_CONSTRAINT"
+        }
+    }
+}
diff --git a/room/compiler/src/main/kotlin/android/arch/persistence/room/processor/PojoProcessor.kt b/room/compiler/src/main/kotlin/android/arch/persistence/room/processor/PojoProcessor.kt
new file mode 100644
index 0000000..a03ac7e
--- /dev/null
+++ b/room/compiler/src/main/kotlin/android/arch/persistence/room/processor/PojoProcessor.kt
@@ -0,0 +1,518 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.arch.persistence.room.processor
+
+import android.arch.persistence.room.Relation
+import android.arch.persistence.room.ColumnInfo
+import android.arch.persistence.room.Embedded
+import android.arch.persistence.room.Ignore
+import android.arch.persistence.room.ext.getAllFieldsIncludingPrivateSupers
+import android.arch.persistence.room.ext.getAnnotationValue
+import android.arch.persistence.room.ext.getAsString
+import android.arch.persistence.room.ext.getAsStringList
+import android.arch.persistence.room.ext.hasAnnotation
+import android.arch.persistence.room.ext.hasAnyOf
+import android.arch.persistence.room.ext.isCollection
+import android.arch.persistence.room.ext.toClassType
+import android.arch.persistence.room.processor.ProcessorErrors.CANNOT_FIND_GETTER_FOR_FIELD
+import android.arch.persistence.room.processor.ProcessorErrors.CANNOT_FIND_SETTER_FOR_FIELD
+import android.arch.persistence.room.processor.ProcessorErrors.CANNOT_FIND_TYPE
+import android.arch.persistence.room.processor.ProcessorErrors.POJO_FIELD_HAS_DUPLICATE_COLUMN_NAME
+import android.arch.persistence.room.processor.cache.Cache
+import android.arch.persistence.room.vo.CallType
+import android.arch.persistence.room.vo.Constructor
+import android.arch.persistence.room.vo.Field
+import android.arch.persistence.room.vo.FieldGetter
+import android.arch.persistence.room.vo.EmbeddedField
+import android.arch.persistence.room.vo.Entity
+import android.arch.persistence.room.vo.FieldSetter
+import android.arch.persistence.room.vo.Pojo
+import com.google.auto.common.AnnotationMirrors
+import com.google.auto.common.MoreElements
+import com.google.auto.common.MoreTypes
+import javax.lang.model.element.Element
+import javax.lang.model.element.ExecutableElement
+import javax.lang.model.element.Modifier
+import javax.lang.model.element.Modifier.ABSTRACT
+import javax.lang.model.element.Modifier.PRIVATE
+import javax.lang.model.element.Modifier.PROTECTED
+import javax.lang.model.element.Modifier.PUBLIC
+import javax.lang.model.element.Modifier.STATIC
+import javax.lang.model.element.TypeElement
+import javax.lang.model.element.VariableElement
+import javax.lang.model.type.DeclaredType
+import javax.lang.model.type.TypeKind
+import javax.lang.model.type.TypeMirror
+import javax.lang.model.util.ElementFilter
+
+/**
+ * Processes any class as if it is a Pojo.
+ */
+class PojoProcessor(baseContext: Context, val element: TypeElement,
+                    val bindingScope: FieldProcessor.BindingScope,
+                    val parent: EmbeddedField?) {
+    val context = baseContext.fork(element)
+    companion object {
+        val PROCESSED_ANNOTATIONS = listOf(ColumnInfo::class, Embedded::class,
+                    Relation::class)
+    }
+    fun process() : Pojo {
+        return context.cache.pojos.get(Cache.PojoKey(element, bindingScope, parent), {
+            doProcess()
+        })
+    }
+
+    private fun doProcess(): Pojo {
+        // TODO handle recursion: b/35980205
+        val declaredType = MoreTypes.asDeclared(element.asType())
+        // TODO handle conflicts with super: b/35568142
+        val allFields = element.getAllFieldsIncludingPrivateSupers(context.processingEnv)
+                .filter {
+                    !it.hasAnnotation(Ignore::class) && !it.hasAnyOf(Modifier.STATIC)
+                }
+                .groupBy { field ->
+                    context.checker.check(
+                            PROCESSED_ANNOTATIONS.count { field.hasAnnotation(it) } < 2, field,
+                            ProcessorErrors.CANNOT_USE_MORE_THAN_ONE_POJO_FIELD_ANNOTATION
+                    )
+                    if (field.hasAnnotation(Embedded::class)) {
+                        Embedded::class
+                    } else if (field.hasAnnotation(Relation::class)) {
+                        Relation::class
+                    } else {
+                        null
+                    }
+                }
+        val myFields = allFields[null]
+                ?.map {
+                    FieldProcessor(
+                            baseContext = context,
+                            containing = declaredType,
+                            element = it,
+                            bindingScope = bindingScope,
+                            fieldParent = parent).process()
+                } ?: emptyList()
+
+        val embeddedFields = allFields[Embedded::class]
+                ?.map {
+                    processEmbeddedField(declaredType, it)
+                } ?: emptyList()
+        val subFields = embeddedFields.flatMap { it.pojo.fields }
+
+        val fields = myFields + subFields
+
+        val myRelationsList = allFields[Relation::class]
+                ?.map {
+                    processRelationField(fields, declaredType, it)
+                }
+                ?.filterNotNull() ?: emptyList()
+
+        val subRelations = embeddedFields.flatMap { it.pojo.relations }
+
+        val relations = myRelationsList + subRelations
+
+        fields.groupBy { it.columnName }
+                .filter { it.value.size > 1 }
+                .forEach {
+                    context.logger.e(element, ProcessorErrors.pojoDuplicateFieldNames(
+                            it.key, it.value.map(Field::getPath)
+                    ))
+                    it.value.forEach {
+                        context.logger.e(it.element, POJO_FIELD_HAS_DUPLICATE_COLUMN_NAME)
+                    }
+                }
+        val methods = MoreElements.getLocalAndInheritedMethods(element,
+                context.processingEnv.elementUtils)
+                .filter {
+                    !it.hasAnyOf(PRIVATE, ABSTRACT, STATIC)
+                            && !it.hasAnnotation(Ignore::class)
+                }
+                .map { MoreElements.asExecutable(it) }
+
+        val getterCandidates = methods.filter {
+            it.parameters.size == 0 && it.returnType.kind != TypeKind.VOID
+        }
+
+        val setterCandidates = methods.filter {
+            it.parameters.size == 1 && it.returnType.kind == TypeKind.VOID
+        }
+        // don't try to find a constructor for binding to statement.
+        val constructor = if (bindingScope == FieldProcessor.BindingScope.BIND_TO_STMT) {
+            // we don't need to construct this POJO.
+            null
+        } else {
+            chooseConstructor(myFields, embeddedFields)
+        }
+
+        assignGetters(myFields, getterCandidates)
+        assignSetters(myFields, setterCandidates, constructor)
+
+        embeddedFields.forEach {
+            assignGetter(it.field, getterCandidates)
+            assignSetter(it.field, setterCandidates, constructor)
+        }
+
+        myRelationsList.forEach {
+            assignGetter(it.field, getterCandidates)
+            assignSetter(it.field, setterCandidates, constructor)
+        }
+
+        val pojo = Pojo(element = element,
+                type = declaredType,
+                fields = fields,
+                embeddedFields = embeddedFields,
+                relations = relations,
+                constructor = constructor)
+        return pojo
+    }
+
+    private fun chooseConstructor(myFields: List<Field>, embedded: List<EmbeddedField>)
+            : Constructor? {
+        val constructors = ElementFilter.constructorsIn(element.enclosedElements)
+                .filterNot { it.hasAnnotation(Ignore::class) || it.hasAnyOf(PRIVATE) }
+        val fieldMap = myFields.associateBy { it.name }
+        val embeddedMap = embedded.associateBy { it.field.name }
+        val typeUtils = context.processingEnv.typeUtils
+        val failedConstructors = mutableMapOf<ExecutableElement, List<Constructor.Param?>>()
+        val goodConstructors = constructors.map { constructor ->
+            val params = constructor.parameters.map param@ { param ->
+                val paramName = param.simpleName.toString()
+                val paramType = param.asType()
+
+                val matches = fun(field: Field?): Boolean {
+                    return if (field == null) {
+                        false
+                    } else if (!field.nameWithVariations.contains(paramName)) {
+                        false
+                    } else {
+                        typeUtils.isAssignable(paramType, field.type)
+                    }
+                }
+
+                val exactFieldMatch = fieldMap[paramName]
+
+                if (matches(exactFieldMatch)) {
+                    return@param Constructor.FieldParam(exactFieldMatch!!)
+                }
+                val exactEmbeddedMatch = embeddedMap[paramName]
+                if (matches(exactEmbeddedMatch?.field)) {
+                    return@param Constructor.EmbeddedParam(exactEmbeddedMatch!!)
+                }
+
+                val matchingFields = myFields.filter {
+                    matches(it)
+                }
+                val embeddedMatches = embedded.filter {
+                    matches(it.field)
+                }
+                if (matchingFields.isEmpty() && embeddedMatches.isEmpty()) {
+                    null
+                } else if (matchingFields.size + embeddedMatches.size == 1) {
+                    if (matchingFields.isNotEmpty()) {
+                        Constructor.FieldParam(matchingFields.first())
+                    } else {
+                        Constructor.EmbeddedParam(embeddedMatches.first())
+                    }
+                } else {
+                    context.logger.e(param, ProcessorErrors.ambigiousConstructor(
+                            pojo = element.qualifiedName.toString(),
+                            paramName = param.simpleName.toString(),
+                            matchingFields = matchingFields.map { it.getPath() }
+                                    + embedded.map { it.field.getPath() }
+                    ))
+                    null
+                }
+            }
+            if (params.any { it == null }) {
+                failedConstructors.put(constructor, params)
+                null
+            } else {
+                @Suppress("UNCHECKED_CAST")
+                Constructor(constructor, params as List<Constructor.Param>)
+            }
+        }.filterNotNull()
+        if (goodConstructors.isEmpty()) {
+            if (failedConstructors.isNotEmpty()) {
+                val failureMsg = failedConstructors.entries.joinToString("\n") { entry ->
+                    val paramsMatching = entry.key.parameters.withIndex().joinToString(", ") {
+                        "${it.value.simpleName} : ${entry.value[it.index]?.log()}"
+                    }
+                    "${entry.key} : [$paramsMatching]"
+                }
+                context.logger.e(element, ProcessorErrors.MISSING_POJO_CONSTRUCTOR +
+                        "\nTried the following constructors but they failed to match:\n$failureMsg")
+            }
+            context.logger.e(element, ProcessorErrors.MISSING_POJO_CONSTRUCTOR)
+            return null
+        }
+        if (goodConstructors.size > 1) {
+            goodConstructors.forEach {
+                context.logger.e(it.element, ProcessorErrors.TOO_MANY_POJO_CONSTRUCTORS)
+            }
+            return null
+        }
+        return goodConstructors.first()
+    }
+
+    private fun processEmbeddedField(declaredType: DeclaredType?, it: Element): EmbeddedField {
+        val fieldPrefix = it.getAnnotationValue(Embedded::class.java, "prefix")
+                ?.toString() ?: ""
+        val inheritedPrefix = parent?.prefix ?: ""
+        val embeddedField = Field(
+                it,
+                it.simpleName.toString(),
+                type = context.processingEnv.typeUtils.asMemberOf(declaredType, it),
+                affinity = null,
+                parent = parent)
+        val subParent = EmbeddedField(
+                field = embeddedField,
+                prefix = inheritedPrefix + fieldPrefix,
+                parent = parent)
+        val asVariable = MoreElements.asVariable(it)
+        subParent.pojo = PojoProcessor(baseContext = context.fork(it),
+                element = MoreTypes.asTypeElement(asVariable.asType()),
+                bindingScope = bindingScope,
+                parent = subParent).process()
+        return subParent
+    }
+
+    private fun processRelationField(myFields : List<Field>, container: DeclaredType?,
+                                     relationElement: VariableElement)
+            : android.arch.persistence.room.vo.Relation? {
+        val annotation = MoreElements.getAnnotationMirror(relationElement, Relation::class.java)
+                .orNull()!!
+        val parentColumnInput = AnnotationMirrors.getAnnotationValue(annotation, "parentColumn")
+                .getAsString("") ?: ""
+
+        val parentField = myFields.firstOrNull {
+            it.columnName == parentColumnInput
+        }
+        if (parentField == null) {
+            context.logger.e(relationElement,
+                    ProcessorErrors.relationCannotFindParentEntityField(
+                            entityName = element.qualifiedName.toString(),
+                            columnName = parentColumnInput,
+                            availableColumns = myFields.map { it.columnName }))
+            return null
+        }
+        // parse it as an entity.
+        val asMember = MoreTypes
+                .asMemberOf(context.processingEnv.typeUtils, container, relationElement)
+        if (asMember.kind == TypeKind.ERROR) {
+            context.logger.e(ProcessorErrors.CANNOT_FIND_TYPE, element)
+            return null
+        }
+        val declared = MoreTypes.asDeclared(asMember)
+        if (!declared.isCollection()) {
+            context.logger.e(relationElement, ProcessorErrors.RELATION_NOT_COLLECTION)
+            return null
+        }
+        val typeArg = declared.typeArguments.first()
+        if (typeArg.kind == TypeKind.ERROR) {
+            context.logger.e(MoreTypes.asTypeElement(typeArg), CANNOT_FIND_TYPE)
+            return null
+        }
+        val typeArgElement = MoreTypes.asTypeElement(typeArg)
+        val entityClassInput = AnnotationMirrors
+                .getAnnotationValue(annotation, "entity").toClassType()
+        val pojo : Pojo
+        val entity : Entity
+        if (entityClassInput == null
+                || MoreTypes.isTypeOf(Any::class.java, entityClassInput)) {
+            entity = EntityProcessor(context, typeArgElement).process()
+            pojo = entity
+        } else {
+            entity = EntityProcessor(context, MoreTypes.asTypeElement(entityClassInput)).process()
+            pojo = PojoProcessor(baseContext = context,
+                    element = typeArgElement,
+                    bindingScope = FieldProcessor.BindingScope.READ_FROM_CURSOR,
+                    parent = parent).process()
+        }
+        // now find the field in the entity.
+        val entityColumnInput = AnnotationMirrors.getAnnotationValue(annotation, "entityColumn")
+                .getAsString() ?: ""
+        val entityField = entity.fields.firstOrNull {
+            it.columnName == entityColumnInput
+        }
+
+        if (entityField == null) {
+            context.logger.e(relationElement,
+                    ProcessorErrors.relationCannotFindEntityField(
+                            entityName = entity.typeName.toString(),
+                            columnName = entityColumnInput,
+                            availableColumns = entity.fields.map { it.columnName }))
+            return null
+        }
+
+        val field = Field(
+                element = relationElement,
+                name = relationElement.simpleName.toString(),
+                type = context.processingEnv.typeUtils.asMemberOf(container, relationElement),
+                affinity = null,
+                parent = parent)
+
+        val projection = AnnotationMirrors.getAnnotationValue(annotation, "projection")
+                .getAsStringList()
+        if(projection.isNotEmpty()) {
+            val missingColumns = projection.filterNot { columnName ->
+                entity.fields.any { columnName == it.columnName }
+            }
+            if (missingColumns.isNotEmpty()) {
+                context.logger.e(relationElement,
+                        ProcessorErrors.relationBadProject(entity.typeName.toString(),
+                                missingColumns, entity.fields.map { it.columnName }))
+            }
+        }
+
+        // if types don't match, row adapter prints a warning
+        return android.arch.persistence.room.vo.Relation(
+                entity = entity,
+                pojo = pojo,
+                field = field,
+                parentField = parentField,
+                entityField = entityField,
+                projection = projection
+        )
+    }
+
+    private fun assignGetters(fields: List<Field>, getterCandidates: List<ExecutableElement>) {
+        fields.forEach { field ->
+            assignGetter(field, getterCandidates)
+        }
+    }
+
+    private fun assignGetter(field: Field, getterCandidates: List<ExecutableElement>) {
+        val success = chooseAssignment(field = field,
+                candidates = getterCandidates,
+                nameVariations = field.getterNameWithVariations,
+                getType = { method ->
+                    method.returnType
+                },
+                assignFromField = {
+                    field.getter = FieldGetter(
+                            name = field.name,
+                            type = field.type,
+                            callType = CallType.FIELD)
+                },
+                assignFromMethod = { match ->
+                    field.getter = FieldGetter(
+                            name = match.simpleName.toString(),
+                            type = match.returnType,
+                            callType = CallType.METHOD)
+                },
+                reportAmbiguity = { matching ->
+                    context.logger.e(field.element,
+                            ProcessorErrors.tooManyMatchingGetters(field, matching))
+                })
+        context.checker.check(success, field.element, CANNOT_FIND_GETTER_FOR_FIELD)
+    }
+
+    private fun assignSetters(fields: List<Field>, setterCandidates: List<ExecutableElement>,
+                              constructor : Constructor?) {
+        fields.forEach { field ->
+            assignSetter(field, setterCandidates, constructor)
+        }
+    }
+
+    private fun assignSetter(field: Field, setterCandidates: List<ExecutableElement>,
+                             constructor: Constructor?) {
+        if (constructor != null && constructor.hasField(field)) {
+            field.setter = FieldSetter(field.name, field.type, CallType.CONSTRUCTOR)
+            return
+        }
+        val success = chooseAssignment(field = field,
+                candidates = setterCandidates,
+                nameVariations = field.setterNameWithVariations,
+                getType = { method ->
+                    method.parameters.first().asType()
+                },
+                assignFromField = {
+                    field.setter = FieldSetter(
+                            name = field.name,
+                            type = field.type,
+                            callType = CallType.FIELD)
+                },
+                assignFromMethod = { match ->
+                    val paramType = match.parameters.first().asType()
+                    field.setter = FieldSetter(
+                            name = match.simpleName.toString(),
+                            type = paramType,
+                            callType = CallType.METHOD)
+                },
+                reportAmbiguity = { matching ->
+                    context.logger.e(field.element,
+                            ProcessorErrors.tooManyMatchingSetter(field, matching))
+                })
+        context.checker.check(success, field.element, CANNOT_FIND_SETTER_FOR_FIELD)
+    }
+
+    /**
+     * Finds a setter/getter from available list of methods.
+     * It returns true if assignment is successful, false otherwise.
+     * At worst case, it sets to the field as if it is accessible so that the rest of the
+     * compilation can continue.
+     */
+    private fun chooseAssignment(field: Field, candidates: List<ExecutableElement>,
+                                 nameVariations: List<String>,
+                                 getType: (ExecutableElement) -> TypeMirror,
+                                 assignFromField: () -> Unit,
+                                 assignFromMethod: (ExecutableElement) -> Unit,
+                                 reportAmbiguity: (List<String>) -> Unit): Boolean {
+        if (field.element.hasAnyOf(PUBLIC)) {
+            assignFromField()
+            return true
+        }
+        val types = context.processingEnv.typeUtils
+        val matching = candidates
+                .filter {
+                    types.isSameType(field.element.asType(), getType(it))
+                            && field.nameWithVariations.contains(it.simpleName.toString())
+                            || nameVariations.contains(it.simpleName.toString())
+                }
+                .groupBy {
+                    if (it.hasAnyOf(PUBLIC)) PUBLIC else PROTECTED
+                }
+        if (matching.isEmpty()) {
+            // we always assign to avoid NPEs in the rest of the compilation.
+            assignFromField()
+            // if field is not private, assume it works (if we are on the same package).
+            // if not, compiler will tell, we didn't have any better alternative anyways.
+            return !field.element.hasAnyOf(PRIVATE)
+        }
+        val match = verifyAndChooseOneFrom(matching[PUBLIC], reportAmbiguity) ?:
+                verifyAndChooseOneFrom(matching[PROTECTED], reportAmbiguity)
+        if (match == null) {
+            assignFromField()
+            return false
+        } else {
+            assignFromMethod(match)
+            return true
+        }
+    }
+
+    private fun verifyAndChooseOneFrom(candidates: List<ExecutableElement>?,
+                                       reportAmbiguity: (List<String>) -> Unit)
+            : ExecutableElement? {
+        if (candidates == null) {
+            return null
+        }
+        if (candidates.size > 1) {
+            reportAmbiguity(candidates.map { it.simpleName.toString() })
+        }
+        return candidates.first()
+    }
+}
diff --git a/room/compiler/src/main/kotlin/android/arch/persistence/room/processor/ProcessorErrors.kt b/room/compiler/src/main/kotlin/android/arch/persistence/room/processor/ProcessorErrors.kt
new file mode 100644
index 0000000..fc83b30
--- /dev/null
+++ b/room/compiler/src/main/kotlin/android/arch/persistence/room/processor/ProcessorErrors.kt
@@ -0,0 +1,443 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.arch.persistence.room.processor
+
+import android.arch.persistence.room.Delete
+import android.arch.persistence.room.Insert
+import android.arch.persistence.room.Query
+import android.arch.persistence.room.Update
+import android.arch.persistence.room.ext.RoomTypeNames
+import android.arch.persistence.room.parser.SQLTypeAffinity
+import android.arch.persistence.room.vo.CustomTypeConverter
+import android.arch.persistence.room.vo.Field
+import com.squareup.javapoet.TypeName
+import javax.lang.model.type.TypeMirror
+
+object ProcessorErrors {
+    private fun String.trim(): String {
+        return this.trimIndent().replace(System.lineSeparator(), " ")
+    }
+    val MISSING_QUERY_ANNOTATION = "Query methods must be annotated with ${Query::class.java}"
+    val MISSING_INSERT_ANNOTATION = "Insertion methods must be annotated with ${Insert::class.java}"
+    val MISSING_DELETE_ANNOTATION = "Deletion methods must be annotated with ${Delete::class.java}"
+    val MISSING_UPDATE_ANNOTATION = "Update methods must be annotated with ${Update::class.java}"
+    val INVALID_ON_CONFLICT_VALUE = "On conflict value must be one of @OnConflictStrategy values."
+    val INVALID_INSERTION_METHOD_RETURN_TYPE = "Methods annotated with @Insert can return either" +
+            " void, long, Long, long[], Long[] or List<Long>."
+
+    fun insertionMethodReturnTypeMismatch(definedReturn : TypeName,
+                                          expectedReturnTypes : List<TypeName>) : String {
+        return "Method returns $definedReturn but it should return one of the following: `" +
+                expectedReturnTypes.joinToString(", ") + "`. If you want to return the list of" +
+                " row ids from the query, your insertion method can receive only 1 parameter."
+    }
+
+    val ABSTRACT_METHOD_IN_DAO_MISSING_ANY_ANNOTATION = "Abstract method in DAO must be annotated" +
+            " with ${Query::class.java} AND ${Insert::class.java}"
+    val CANNOT_USE_MORE_THAN_ONE_DAO_METHOD_ANNOTATION = "A DAO method can be annotated with only" +
+            " one of the following:" + DaoProcessor.PROCESSED_ANNOTATIONS.joinToString(",") {
+        it.java.simpleName
+    }
+    val CANNOT_RESOLVE_RETURN_TYPE = "Cannot resolve return type for %s"
+    val CANNOT_USE_UNBOUND_GENERICS_IN_QUERY_METHODS = "Cannot use unbound generics in query" +
+            " methods. It must be bound to a type through base Dao class."
+    val CANNOT_USE_UNBOUND_GENERICS_IN_INSERTION_METHODS = "Cannot use unbound generics in" +
+            " insertion methods. It must be bound to a type through base Dao class."
+    val CANNOT_USE_UNBOUND_GENERICS_IN_ENTITY_FIELDS = "Cannot use unbound fields in entities."
+    val CANNOT_USE_UNBOUND_GENERICS_IN_DAO_CLASSES = "Cannot use unbound generics in Dao classes." +
+            " If you are trying to create a base DAO, create a normal class, extend it with type" +
+            " params then mark the subclass with @Dao."
+    val CANNOT_FIND_GETTER_FOR_FIELD = "Cannot find getter for field."
+    val CANNOT_FIND_SETTER_FOR_FIELD = "Cannot find setter for field."
+    val MISSING_PRIMARY_KEY = "An entity must have at least 1 field annotated with @PrimaryKey"
+    val AUTO_INCREMENTED_PRIMARY_KEY_IS_NOT_INT = "If a primary key is annotated with" +
+            " autoGenerate, its type must be int, Integer, long or Long."
+    val AUTO_INCREMENT_EMBEDDED_HAS_MULTIPLE_FIELDS = "When @PrimaryKey annotation is used on a" +
+            " field annotated with @Embedded, the embedded class should have only 1 field."
+
+    fun multiplePrimaryKeyAnnotations(primaryKeys: List<String>): String {
+        return """
+                You cannot have multiple primary keys defined in an Entity. If you
+                want to declare a composite primary key, you should use @Entity#primaryKeys and
+                not use @PrimaryKey. Defined Primary Keys:
+                ${primaryKeys.joinToString(", ")}""".trim()
+    }
+
+    fun primaryKeyColumnDoesNotExist(columnName: String, allColumns: List<String>): String {
+        return "$columnName referenced in the primary key does not exists in the Entity." +
+                " Available column names:${allColumns.joinToString(", ")}"
+    }
+
+    val DAO_MUST_BE_AN_ABSTRACT_CLASS_OR_AN_INTERFACE = "Dao class must be an abstract class or" +
+            " an interface"
+    val DATABASE_MUST_BE_ANNOTATED_WITH_DATABASE = "Database must be annotated with @Database"
+    val DAO_MUST_BE_ANNOTATED_WITH_DAO = "Dao class must be annotated with @Dao"
+
+    fun daoMustHaveMatchingConstructor(daoName: String, dbName: String): String {
+        return """
+                $daoName needs to have either an empty constructor or a constructor that takes
+                $dbName as its only parameter.
+                """.trim()
+    }
+
+    val ENTITY_MUST_BE_ANNOTATED_WITH_ENTITY = "Entity class must be annotated with @Entity"
+    val DATABASE_ANNOTATION_MUST_HAVE_LIST_OF_ENTITIES = "@Database annotation must specify list" +
+            " of entities"
+    val COLUMN_NAME_CANNOT_BE_EMPTY = "Column name cannot be blank. If you don't want to set it" +
+            ", just remove the @ColumnInfo annotation or use @ColumnInfo.INHERIT_FIELD_NAME."
+
+    val ENTITY_TABLE_NAME_CANNOT_BE_EMPTY = "Entity table name cannot be blank. If you don't want" +
+            " to set it, just remove the tableName property."
+
+    val CANNOT_BIND_QUERY_PARAMETER_INTO_STMT = "Query method parameters should either be a" +
+            " type that can be converted into a database column or a List / Array that contains" +
+            " such type. You can consider adding a Type Adapter for this."
+
+    val QUERY_PARAMETERS_CANNOT_START_WITH_UNDERSCORE = "Query/Insert method parameters cannot " +
+            "start with underscore (_)."
+
+    val CANNOT_FIND_QUERY_RESULT_ADAPTER = "Not sure how to convert a Cursor to this method's " +
+            "return type"
+
+    val INSERTION_DOES_NOT_HAVE_ANY_PARAMETERS_TO_INSERT = "Method annotated with" +
+            " @Insert but does not have any parameters to insert."
+
+    val DELETION_MISSING_PARAMS = "Method annotated with" +
+            " @Delete but does not have any parameters to delete."
+
+    val UPDATE_MISSING_PARAMS = "Method annotated with" +
+            " @Update but does not have any parameters to update."
+
+    val CANNOT_FIND_ENTITY_FOR_SHORTCUT_QUERY_PARAMETER = "Type of the parameter must be a class " +
+            "annotated with @Entity or a collection/array of it."
+
+    val DB_MUST_EXTEND_ROOM_DB = "Classes annotated with @Database should extend " +
+            RoomTypeNames.ROOM_DB
+
+    val LIVE_DATA_QUERY_WITHOUT_SELECT = "LiveData return type can only be used with SELECT" +
+            " queries."
+
+    private val TOO_MANY_MATCHING_GETTERS = "Ambiguous getter for %s. All of the following " +
+            "match: %s. You can @Ignore the ones that you don't want to match."
+
+    fun tooManyMatchingGetters(field: Field, methodNames: List<String>): String {
+        return TOO_MANY_MATCHING_GETTERS.format(field, methodNames.joinToString(", "))
+    }
+
+    private val TOO_MANY_MATCHING_SETTERS = "Ambiguous setter for %s. All of the following " +
+            "match: %s. You can @Ignore the ones that you don't want to match."
+
+    fun tooManyMatchingSetter(field: Field, methodNames: List<String>): String {
+        return TOO_MANY_MATCHING_SETTERS.format(field, methodNames.joinToString(", "))
+    }
+
+    val CANNOT_FIND_COLUMN_TYPE_ADAPTER = "Cannot figure out how to save this field into" +
+            " database. You can consider adding a type converter for it."
+
+    val CANNOT_FIND_STMT_BINDER = "Cannot figure out how to bind this field into a statement."
+
+    val CANNOT_FIND_CURSOR_READER = "Cannot figure out how to read this field from a cursor."
+
+    private val MISSING_PARAMETER_FOR_BIND = "Each bind variable in the query must have a" +
+            " matching method parameter. Cannot find method parameters for %s."
+
+    fun missingParameterForBindVariable(bindVarName: List<String>): String {
+        return MISSING_PARAMETER_FOR_BIND.format(bindVarName.joinToString(", "))
+    }
+
+    private val UNUSED_QUERY_METHOD_PARAMETER = "Unused parameter%s: %s"
+    fun unusedQueryMethodParameter(unusedParams: List<String>): String {
+        return UNUSED_QUERY_METHOD_PARAMETER.format(
+                if (unusedParams.size > 1) "s" else "",
+                unusedParams.joinToString(","))
+    }
+
+    private val DUPLICATE_TABLES = "Table name \"%s\" is used by multiple entities: %s"
+    fun duplicateTableNames(tableName: String, entityNames: List<String>): String {
+        return DUPLICATE_TABLES.format(tableName, entityNames.joinToString(", "))
+    }
+
+    val DELETION_METHODS_MUST_RETURN_VOID_OR_INT = "Deletion methods must either return void or" +
+            " return int (the number of deleted rows)."
+
+    val UPDATE_METHODS_MUST_RETURN_VOID_OR_INT = "Update methods must either return void or" +
+            " return int (the number of updated rows)."
+
+    val DAO_METHOD_CONFLICTS_WITH_OTHERS = "Dao method has conflicts."
+
+    fun duplicateDao(dao: TypeName, methodNames: List<String>): String {
+        return """
+                All of these functions [${methodNames.joinToString(", ")}] return the same DAO
+                class [$dao].
+                A database can use a DAO only once so you should remove ${methodNames.size - 1} of
+                these conflicting DAO methods. If you are implementing any of these to fulfill an
+                interface, don't make it abstract, instead, implement the code that calls the
+                other one.
+                """.trim()
+    }
+
+    fun cursorPojoMismatch(pojoTypeName: TypeName,
+                           unusedColumns: List<String>, allColumns: List<String>,
+                           unusedFields: List<Field>, allFields: List<Field>): String {
+        val unusedColumnsWarning = if (unusedColumns.isNotEmpty()) {
+            """
+                The query returns some columns [${unusedColumns.joinToString(", ")}] which are not
+                use by $pojoTypeName. You can use @ColumnInfo annotation on the fields to specify
+                the mapping.
+            """.trim()
+        } else {
+            ""
+        }
+        val unusedFieldsWarning = if (unusedFields.isNotEmpty()) {
+            """
+                $pojoTypeName has some fields
+                [${unusedFields.joinToString(", ") { it.columnName }}] which are not returned by the
+                query. If they are not supposed to be read from the result, you can mark them with
+                @Ignore annotation.
+            """.trim()
+        } else {
+            ""
+        }
+        return """
+            $unusedColumnsWarning
+            $unusedFieldsWarning
+            You can suppress this warning by annotating the method with
+            @SuppressWarnings(RoomWarnings.CURSOR_MISMATCH).
+            Columns returned by the query: ${allColumns.joinToString(", ")}.
+            Fields in $pojoTypeName: ${allFields.joinToString(", ") { it.columnName }}.
+            """.trim()
+    }
+
+    val TYPE_CONVERTER_UNBOUND_GENERIC = "Cannot use unbound generics in Type Converters."
+    val TYPE_CONVERTER_BAD_RETURN_TYPE = "Invalid return type for a type converter."
+    val TYPE_CONVERTER_MUST_RECEIVE_1_PARAM = "Type converters must receive 1 parameter."
+    val TYPE_CONVERTER_EMPTY_CLASS = "Class is referenced as a converter but it does not have any" +
+            " converter methods."
+    val TYPE_CONVERTER_MISSING_NOARG_CONSTRUCTOR = "Classes that are used as TypeConverters must" +
+            " have no-argument public constructors."
+    val TYPE_CONVERTER_MUST_BE_PUBLIC = "Type converters must be public."
+
+    fun duplicateTypeConverters(converters: List<CustomTypeConverter>): String {
+        return "Multiple methods define the same conversion. Conflicts with these:" +
+                " ${converters.joinToString(", ") { it.toString() }}"
+    }
+
+    // TODO must print field paths.
+    val POJO_FIELD_HAS_DUPLICATE_COLUMN_NAME = "Field has non-unique column name."
+
+    fun pojoDuplicateFieldNames(columnName: String, fieldPaths: List<String>): String {
+        return "Multiple fields have the same columnName: $columnName." +
+                " Field names: ${fieldPaths.joinToString(", ")}."
+    }
+
+    fun embeddedPrimaryKeyIsDropped(entityQName: String, fieldName: String): String {
+        return "Primary key constraint on $fieldName is ignored when being merged into " +
+                entityQName
+    }
+
+    val INDEX_COLUMNS_CANNOT_BE_EMPTY = "List of columns in an index cannot be empty"
+
+    fun indexColumnDoesNotExist(columnName: String, allColumns: List<String>): String {
+        return "$columnName referenced in the index does not exists in the Entity." +
+                " Available column names:${allColumns.joinToString(", ")}"
+    }
+
+    fun duplicateIndexInEntity(indexName: String): String {
+        return "There are multiple indices with name $indexName. This happen if you've declared" +
+                " the same index multiple times or different indices have the same name. See" +
+                " @Index documentation for details."
+    }
+
+    fun duplicateIndexInDatabase(indexName: String, indexPaths: List<String>): String {
+        return "There are multiple indices with name $indexName. You should rename " +
+                "${indexPaths.size - 1} of these to avoid the conflict:" +
+                "${indexPaths.joinToString(", ")}."
+    }
+
+    fun droppedEmbeddedFieldIndex(fieldPath: String, grandParent: String): String {
+        return "The index will be dropped when being merged into $grandParent" +
+                "($fieldPath). You must re-declare it in $grandParent if you want to index this" +
+                " field in $grandParent."
+    }
+
+    fun droppedEmbeddedIndex(entityName: String, fieldPath: String, grandParent: String)
+            : String {
+        return "Indices defined in $entityName will be dropped when it is merged into" +
+                " $grandParent ($fieldPath). You can re-declare them in $grandParent."
+    }
+
+    fun droppedSuperClassIndex(childEntity: String, superEntity: String): String {
+        return "Indices defined in $superEntity will NOT be re-used in $childEntity. If you want" +
+                " to inherit them, you must re-declare them in $childEntity." +
+                " Alternatively, you can set inheritSuperIndices to true in the @Entity annotation."
+    }
+
+    fun droppedSuperClassFieldIndex(fieldName: String, childEntity: String,
+                                    superEntity: String): String {
+        return "Index defined on field `$fieldName` in $superEntity will NOT be re-used in" +
+                " $childEntity. " +
+                "If you want to inherit it, you must re-declare it in $childEntity." +
+                " Alternatively, you can set inheritSuperIndices to true in the @Entity annotation."
+    }
+
+    val RELATION_NOT_COLLECTION = "Fields annotated with @Relation must be a List or Set."
+
+    fun relationCannotFindEntityField(entityName : String, columnName: String,
+                                      availableColumns: List<String>) : String {
+        return "Cannot find the child entity column `$columnName` in $entityName." +
+                " Options: ${availableColumns.joinToString(", ")}"
+    }
+
+    fun relationCannotFindParentEntityField(entityName : String, columnName: String,
+                                            availableColumns: List<String>) : String {
+        return "Cannot find the parent entity column `$columnName` in $entityName." +
+                " Options: ${availableColumns.joinToString(", ")}"
+    }
+
+    val RELATION_IN_ENTITY = "Entities cannot have relations."
+
+    val CANNOT_FIND_TYPE = "Cannot find type."
+
+    fun relationAffinityMismatch(parentColumn: String, childColumn: String,
+                                 parentAffinity : SQLTypeAffinity?,
+                                 childAffinity : SQLTypeAffinity?) : String {
+        return """
+        The affinity of parent column ($parentColumn : $parentAffinity) does not match the type
+        affinity of the child column ($childColumn : $childAffinity).
+        """.trim()
+    }
+
+    val CANNOT_USE_MORE_THAN_ONE_POJO_FIELD_ANNOTATION = "A field can be annotated with only" +
+            " one of the following:" + PojoProcessor.PROCESSED_ANNOTATIONS.joinToString(",") {
+        it.java.simpleName
+    }
+
+    fun relationBadProject(entityQName : String, missingColumnNames : List<String>,
+                           availableColumnNames : List<String>) : String {
+        return """
+        $entityQName does not have the following columns: ${missingColumnNames.joinToString(",")}.
+        Available columns are: ${availableColumnNames.joinToString(",")}
+        """.trim()
+    }
+
+    val MISSING_SCHEMA_EXPORT_DIRECTORY = "Schema export directory is not provided to the" +
+            " annotation processor so we cannot export the schema. You can either provide" +
+            " `room.schemaLocation` annotation processor argument OR set exportSchema to false."
+
+    val INVALID_FOREIGN_KEY_ACTION = "Invalid foreign key action. It must be one of the constants" +
+            " defined in ForeignKey.Action"
+
+    fun foreignKeyNotAnEntity(className : String) : String {
+        return """
+        Classes referenced in Foreign Key annotations must be @Entity classes. $className is not
+        an entity
+        """.trim()
+    }
+
+    val FOREIGN_KEY_CANNOT_FIND_PARENT = "Cannot find parent entity class."
+
+    fun foreignKeyChildColumnDoesNotExist(columnName: String, allColumns: List<String>): String {
+        return "($columnName) referenced in the foreign key does not exists in the Entity." +
+                " Available column names:${allColumns.joinToString(", ")}"
+    }
+
+    fun foreignKeyParentColumnDoesNotExist(parentEntity : String,
+                                           missingColumn: String,
+                                           allColumns : List<String>): String {
+        return "($missingColumn) does not exist in $parentEntity. Available columns are" +
+                " ${allColumns.joinToString(",")}"
+    }
+
+    val FOREIGN_KEY_EMPTY_CHILD_COLUMN_LIST = "Must specify at least 1 column name for the child"
+
+    val FOREIGN_KEY_EMPTY_PARENT_COLUMN_LIST = "Must specify at least 1 column name for the parent"
+
+    fun foreignKeyColumnNumberMismatch(childColumns : List<String>, parentColumns : List<String>)
+            : String {
+        return """
+                Number of child columns in foreign key must match number of parent columns.
+                Child reference has ${childColumns.joinToString(",")} and parent reference has
+                ${parentColumns.joinToString(",")}
+               """.trim()
+    }
+
+    fun foreignKeyMissingParentEntityInDatabase(parentTable : String, childEntity : String)
+            : String {
+        return """
+                $parentTable table referenced in the foreign keys of $childEntity does not exist in
+                the database. Maybe you forgot to add the referenced entity in the entities list of
+                the @Database annotation?""".trim()
+    }
+
+    fun foreignKeyMissingIndexInParent(parentEntity : String, parentColumns: List<String>,
+                                       childEntity : String, childColumns: List<String>): String {
+        return """
+                $childEntity has a foreign key (${childColumns.joinToString(",")}) that references
+                $parentEntity (${parentColumns.joinToString(",")}) but $parentEntity does not have
+                a unique index on those columns nor the columns are its primary key.
+                SQLite requires having a unique constraint on referenced parent columns so you must
+                add a unique index to $parentEntity that has
+                (${parentColumns.joinToString(",")}) column(s).
+               """.trim()
+    }
+
+    fun foreignKeyMissingIndexInChildColumns(childColumns: List<String>) : String {
+        return """
+                (${childColumns.joinToString(",")}) column(s) reference a foreign key but
+                they are not part of an index. This may trigger full table scans whenever parent
+                table is modified so you are highly advised to create an index that covers these
+                columns.
+               """.trim()
+    }
+
+    fun foreignKeyMissingIndexInChildColumn(childColumn: String) : String {
+        return """
+                $childColumn column references a foreign key but it is not part of an index. This
+                may trigger full table scans whenever parent table is modified so you are highly
+                advised to create an index that covers this column.
+               """.trim()
+    }
+
+    fun shortcutEntityIsNotInDatabase(database : String, dao : String, entity : String) : String {
+        return """
+                $dao is part of $database but this entity is not in the database. Maybe you forgot
+                to add $entity to the entities section of the @Database?
+                """.trim()
+    }
+    val MISSING_ROOM_RXJAVA2_ARTIFACT = "To use RxJava2 features, you must add `rxjava2`" +
+            " artifact from Room as a dependency. android.arch.persistence.room:rxjava2:<version>"
+
+    fun ambigiousConstructor(pojo : String, paramName:String, matchingFields : List<String>)
+            : String {
+        return """
+            Ambiguous constructor. The parameter ($paramName) in $pojo matches multiple fields:
+            [${matchingFields.joinToString(",")}]. If you don't want to use this constructor,
+            you can annotate it with @Ignore. If you want Room to use this constructor, you can
+            rename the parameters to exactly match the field name to fix the ambiguity.
+            """.trim()
+    }
+
+    val MISSING_POJO_CONSTRUCTOR = """
+            Entities and Pojos must have a usable public constructor. You can have an empty
+            constructor or a constructor whose parameters match the fields (by name and type).
+            """.trim()
+
+    val TOO_MANY_POJO_CONSTRUCTORS = """
+            Room cannot pick a constructor since multiple constructors are suitable. Try to annotate
+            unwanted constructors with @Ignore.
+            """.trim()
+}
diff --git a/room/compiler/src/main/kotlin/android/arch/persistence/room/processor/QueryMethodProcessor.kt b/room/compiler/src/main/kotlin/android/arch/persistence/room/processor/QueryMethodProcessor.kt
new file mode 100644
index 0000000..fc60074
--- /dev/null
+++ b/room/compiler/src/main/kotlin/android/arch/persistence/room/processor/QueryMethodProcessor.kt
@@ -0,0 +1,123 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.arch.persistence.room.processor
+
+import android.arch.persistence.room.Query
+import android.arch.persistence.room.SkipQueryVerification
+import android.arch.persistence.room.ext.hasAnnotation
+import android.arch.persistence.room.parser.ParsedQuery
+import android.arch.persistence.room.parser.QueryType
+import android.arch.persistence.room.parser.SqlParser
+import android.arch.persistence.room.solver.query.result.LiveDataQueryResultBinder
+import android.arch.persistence.room.verifier.DatabaseVerificaitonErrors
+import android.arch.persistence.room.verifier.DatabaseVerifier
+import android.arch.persistence.room.vo.QueryMethod
+import android.arch.persistence.room.vo.QueryParameter
+import com.google.auto.common.AnnotationMirrors
+import com.google.auto.common.MoreElements
+import com.google.auto.common.MoreTypes
+import com.squareup.javapoet.TypeName
+import javax.lang.model.element.ExecutableElement
+import javax.lang.model.type.DeclaredType
+import javax.lang.model.type.TypeKind
+
+class QueryMethodProcessor(baseContext: Context,
+                           val containing: DeclaredType,
+                           val executableElement: ExecutableElement,
+                           val dbVerifier: DatabaseVerifier? = null) {
+    val context = baseContext.fork(executableElement)
+
+    fun process(): QueryMethod {
+        val asMember = context.processingEnv.typeUtils.asMemberOf(containing, executableElement)
+        val executableType = MoreTypes.asExecutable(asMember)
+
+        val annotation = MoreElements.getAnnotationMirror(executableElement,
+                Query::class.java).orNull()
+        context.checker.check(annotation != null, executableElement,
+                ProcessorErrors.MISSING_QUERY_ANNOTATION)
+
+        val query = if (annotation != null) {
+            val query = SqlParser.parse(
+                    AnnotationMirrors.getAnnotationValue(annotation, "value").value.toString())
+            context.checker.check(query.errors.isEmpty(), executableElement,
+                    query.errors.joinToString("\n"))
+            if (!executableElement.hasAnnotation(SkipQueryVerification::class)) {
+                query.resultInfo = dbVerifier?.analyze(query.original)
+            }
+            if (query.resultInfo?.error != null) {
+                context.logger.e(executableElement,
+                        DatabaseVerificaitonErrors.cannotVerifyQuery(query.resultInfo!!.error!!))
+            }
+
+            context.checker.check(executableType.returnType.kind != TypeKind.ERROR,
+                    executableElement, ProcessorErrors.CANNOT_RESOLVE_RETURN_TYPE,
+                    executableElement)
+            query
+        } else {
+            ParsedQuery.MISSING
+        }
+
+        val returnTypeName = TypeName.get(executableType.returnType)
+        context.checker.notUnbound(returnTypeName, executableElement,
+                ProcessorErrors.CANNOT_USE_UNBOUND_GENERICS_IN_QUERY_METHODS)
+
+        if (query.type == QueryType.DELETE) {
+            context.checker.check(
+                    returnTypeName == TypeName.VOID || returnTypeName == TypeName.INT,
+                    executableElement,
+                    ProcessorErrors.DELETION_METHODS_MUST_RETURN_VOID_OR_INT
+            )
+        }
+        val resultBinder = context.typeAdapterStore
+                .findQueryResultBinder(executableType.returnType, query)
+        context.checker.check(resultBinder.adapter != null || query.type != QueryType.SELECT,
+                executableElement, ProcessorErrors.CANNOT_FIND_QUERY_RESULT_ADAPTER)
+        if (resultBinder is LiveDataQueryResultBinder) {
+            context.checker.check(query.type == QueryType.SELECT, executableElement,
+                    ProcessorErrors.LIVE_DATA_QUERY_WITHOUT_SELECT)
+        }
+
+        val queryMethod = QueryMethod(
+                element = executableElement,
+                query = query,
+                name = executableElement.simpleName.toString(),
+                returnType = executableType.returnType,
+                parameters = executableElement.parameters
+                        .map { QueryParameterProcessor(
+                                baseContext = context,
+                                containing = containing,
+                                element = it).process() },
+                queryResultBinder = resultBinder)
+
+        val missing = queryMethod.sectionToParamMapping
+                .filter { it.second == null }
+                .map { it.first.text }
+        if (missing.isNotEmpty()) {
+            context.logger.e(executableElement,
+                    ProcessorErrors.missingParameterForBindVariable(missing))
+        }
+
+        val unused = queryMethod.parameters.filterNot { param ->
+            queryMethod.sectionToParamMapping.any { it.second == param }
+        }.map(QueryParameter::name)
+
+        if (unused.isNotEmpty()) {
+            context.logger.e(executableElement, ProcessorErrors.unusedQueryMethodParameter(unused))
+        }
+        return queryMethod
+    }
+}
diff --git a/room/compiler/src/main/kotlin/android/arch/persistence/room/processor/QueryParameterProcessor.kt b/room/compiler/src/main/kotlin/android/arch/persistence/room/processor/QueryParameterProcessor.kt
new file mode 100644
index 0000000..1fe3afe
--- /dev/null
+++ b/room/compiler/src/main/kotlin/android/arch/persistence/room/processor/QueryParameterProcessor.kt
@@ -0,0 +1,41 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.arch.persistence.room.processor
+
+import android.arch.persistence.room.vo.QueryParameter
+import com.google.auto.common.MoreTypes
+import javax.lang.model.element.VariableElement
+import javax.lang.model.type.DeclaredType
+
+class QueryParameterProcessor(baseContext: Context,
+                              val containing: DeclaredType,
+                              val element: VariableElement) {
+    val context = baseContext.fork(element)
+    fun process(): QueryParameter {
+        val asMember = MoreTypes.asMemberOf(context.processingEnv.typeUtils, containing, element)
+        val parameterAdapter = context.typeAdapterStore.findQueryParameterAdapter(asMember)
+        context.checker.check(parameterAdapter != null, element,
+                ProcessorErrors.CANNOT_BIND_QUERY_PARAMETER_INTO_STMT)
+
+        val name = element.simpleName.toString()
+        context.checker.check(!name.startsWith("_"), element,
+                ProcessorErrors.QUERY_PARAMETERS_CANNOT_START_WITH_UNDERSCORE)
+        return QueryParameter(name = name,
+                type = asMember,
+                queryParamAdapter = parameterAdapter)
+    }
+}
diff --git a/room/compiler/src/main/kotlin/android/arch/persistence/room/processor/ShortcutMethodProcessor.kt b/room/compiler/src/main/kotlin/android/arch/persistence/room/processor/ShortcutMethodProcessor.kt
new file mode 100644
index 0000000..95d16f5
--- /dev/null
+++ b/room/compiler/src/main/kotlin/android/arch/persistence/room/processor/ShortcutMethodProcessor.kt
@@ -0,0 +1,67 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package android.arch.persistence.room.processor
+
+import android.arch.persistence.room.vo.Entity
+import android.arch.persistence.room.vo.ShortcutQueryParameter
+import com.google.auto.common.MoreElements
+import com.google.auto.common.MoreTypes
+import javax.lang.model.element.AnnotationMirror
+import javax.lang.model.element.ExecutableElement
+import javax.lang.model.type.DeclaredType
+import javax.lang.model.type.TypeMirror
+import kotlin.reflect.KClass
+
+/**
+ * Common functionality for shortcut method processors
+ */
+class ShortcutMethodProcessor(baseContext : Context,
+                              val containing: DeclaredType,
+                              val executableElement: ExecutableElement) {
+    val context = baseContext.fork(executableElement)
+    private val asMember = context.processingEnv.typeUtils.asMemberOf(containing, executableElement)
+    private val executableType = MoreTypes.asExecutable(asMember)
+
+    fun extractAnnotation(klass : KClass<out Annotation>,
+                          errorMsg : String) : AnnotationMirror? {
+        val annotation = MoreElements.getAnnotationMirror(executableElement,
+                klass.java).orNull()
+        context.checker.check(annotation != null, executableElement, errorMsg)
+        return annotation
+    }
+
+    fun extractReturnType() : TypeMirror {
+        return executableType.returnType
+    }
+
+    fun extractParams(missingParamError: String)
+            : Pair<Map<String, Entity>, List<ShortcutQueryParameter>> {
+        val params = executableElement.parameters
+                .map { ShortcutParameterProcessor(
+                        baseContext = context,
+                        containing = containing,
+                        element = it).process() }
+        context.checker.check(params.isNotEmpty(), executableElement, missingParamError)
+        val entities = params
+                .filter { it.entityType != null }
+                .associateBy({ it.name }, {
+                    EntityProcessor(
+                            baseContext = context,
+                            element = MoreTypes.asTypeElement(it.entityType)).process()
+                })
+        return Pair(entities, params)
+    }
+}
diff --git a/room/compiler/src/main/kotlin/android/arch/persistence/room/processor/ShortcutParameterProcessor.kt b/room/compiler/src/main/kotlin/android/arch/persistence/room/processor/ShortcutParameterProcessor.kt
new file mode 100644
index 0000000..4f66920
--- /dev/null
+++ b/room/compiler/src/main/kotlin/android/arch/persistence/room/processor/ShortcutParameterProcessor.kt
@@ -0,0 +1,99 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.arch.persistence.room.processor
+
+import android.arch.persistence.room.Entity
+import android.arch.persistence.room.ext.hasAnnotation
+import android.arch.persistence.room.vo.ShortcutQueryParameter
+import com.google.auto.common.MoreTypes
+import javax.lang.model.element.TypeElement
+import javax.lang.model.element.VariableElement
+import javax.lang.model.type.ArrayType
+import javax.lang.model.type.DeclaredType
+import javax.lang.model.type.TypeMirror
+import javax.lang.model.util.ElementFilter
+
+/**
+ * Processes parameters of methods that are annotated with Insert, Delete.
+ */
+class ShortcutParameterProcessor(baseContext : Context,
+                                 val containing: DeclaredType,
+                                 val element: VariableElement) {
+    val context = baseContext.fork(element)
+    fun process(): ShortcutQueryParameter {
+        val asMember = MoreTypes.asMemberOf(context.processingEnv.typeUtils, containing, element)
+        val name = element.simpleName.toString()
+        context.checker.check(!name.startsWith("_"), element,
+                ProcessorErrors.QUERY_PARAMETERS_CANNOT_START_WITH_UNDERSCORE)
+
+        val (entityType, isMultiple) = extractEntityType(asMember)
+        context.checker.check(entityType != null, element,
+                ProcessorErrors.CANNOT_FIND_ENTITY_FOR_SHORTCUT_QUERY_PARAMETER)
+
+        return ShortcutQueryParameter(
+                name = name,
+                type = asMember,
+                entityType = entityType,
+                isMultiple = isMultiple
+        )
+    }
+
+    @Suppress("PLATFORM_CLASS_MAPPED_TO_KOTLIN")
+    fun extractEntityType(typeMirror: TypeMirror) : Pair<TypeMirror?, Boolean> {
+
+        val elementUtils = context.processingEnv.elementUtils
+        val typeUtils = context.processingEnv.typeUtils
+
+        fun verifyAndPair(entityType: TypeMirror, isMultiple : Boolean)
+                : Pair<TypeMirror?, Boolean> {
+            if (!MoreTypes.isType(entityType)) {
+                return Pair(null, isMultiple)
+            }
+            val entityElement = MoreTypes.asElement(entityType)
+            return if (entityElement.hasAnnotation(Entity::class)) {
+                Pair(entityType, isMultiple)
+            } else {
+                Pair(null, isMultiple)
+            }
+        }
+
+        fun extractEntityTypeFromIterator(iterableType: DeclaredType): TypeMirror {
+            ElementFilter.methodsIn(elementUtils
+                    .getAllMembers(typeUtils.asElement(iterableType) as TypeElement)).forEach {
+                if (it.simpleName.toString() == "iterator") {
+                    return MoreTypes.asDeclared(MoreTypes.asExecutable(
+                            typeUtils.asMemberOf(iterableType, it)).returnType)
+                            .typeArguments.first()
+                }
+            }
+            throw IllegalArgumentException("iterator() not found in Iterable $iterableType")
+        }
+
+        val iterableType = typeUtils.erasure(elementUtils
+                .getTypeElement("java.lang.Iterable").asType())
+        if (typeUtils.isAssignable(typeMirror, iterableType)) {
+            val declared = MoreTypes.asDeclared(typeMirror)
+            val entity = extractEntityTypeFromIterator(declared)
+            return verifyAndPair(entity, true)
+        }
+        if (typeMirror is ArrayType) {
+            val entity = typeMirror.componentType
+            return verifyAndPair(entity, true)
+        }
+        return verifyAndPair(typeMirror, false)
+    }
+}
diff --git a/room/compiler/src/main/kotlin/android/arch/persistence/room/processor/SuppressWarningProcessor.kt b/room/compiler/src/main/kotlin/android/arch/persistence/room/processor/SuppressWarningProcessor.kt
new file mode 100644
index 0000000..e2215d9
--- /dev/null
+++ b/room/compiler/src/main/kotlin/android/arch/persistence/room/processor/SuppressWarningProcessor.kt
@@ -0,0 +1,58 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.arch.persistence.room.processor
+
+import android.arch.persistence.room.vo.Warning
+import com.google.auto.common.AnnotationMirrors
+import com.google.auto.common.MoreElements
+import javax.lang.model.element.AnnotationValue
+import javax.lang.model.element.Element
+import javax.lang.model.util.SimpleAnnotationValueVisitor6
+
+/**
+ * A visitor that reads SuppressWarnings annotations and keeps the ones we know about.
+ */
+object SuppressWarningProcessor {
+
+    fun getSuppressedWarnings(element: Element): Set<Warning> {
+        val annotation = MoreElements.getAnnotationMirror(element,
+                SuppressWarnings::class.java).orNull()
+        return if (annotation == null) {
+            emptySet<Warning>()
+        } else {
+            val value = AnnotationMirrors.getAnnotationValue(annotation, "value")
+            if (value == null) {
+                emptySet<Warning>()
+            } else {
+                VISITOR.visit(value)
+            }
+        }
+    }
+
+    private object VISITOR : SimpleAnnotationValueVisitor6<Set<Warning>, String>() {
+        override fun visitArray(values: List<AnnotationValue>?, elementName: String?)
+                : Set<Warning> {
+            return values?.map {
+                Warning.fromPublicKey(it.value.toString())
+            }?.filterNotNull()?.toSet() ?: emptySet()
+        }
+
+        override fun defaultAction(o: Any?, p: String?): Set<Warning> {
+            return emptySet()
+        }
+    }
+}
diff --git a/room/compiler/src/main/kotlin/android/arch/persistence/room/processor/UpdateMethodProcessor.kt b/room/compiler/src/main/kotlin/android/arch/persistence/room/processor/UpdateMethodProcessor.kt
new file mode 100644
index 0000000..452ea4a
--- /dev/null
+++ b/room/compiler/src/main/kotlin/android/arch/persistence/room/processor/UpdateMethodProcessor.kt
@@ -0,0 +1,63 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.arch.persistence.room.processor
+
+import android.arch.persistence.room.OnConflictStrategy.IGNORE
+import android.arch.persistence.room.OnConflictStrategy.REPLACE
+import android.arch.persistence.room.Update
+import android.arch.persistence.room.ext.typeName
+import android.arch.persistence.room.vo.UpdateMethod
+import com.squareup.javapoet.TypeName
+import javax.lang.model.element.ExecutableElement
+import javax.lang.model.type.DeclaredType
+
+class UpdateMethodProcessor(baseContext: Context,
+                              val containing: DeclaredType,
+                              val executableElement: ExecutableElement) {
+    val context = baseContext.fork(executableElement)
+
+    fun process(): UpdateMethod {
+        val delegate = ShortcutMethodProcessor(context, containing, executableElement)
+        val annotation = delegate
+                .extractAnnotation(Update::class, ProcessorErrors.MISSING_UPDATE_ANNOTATION)
+
+        val onConflict = OnConflictProcessor.extractFrom(annotation)
+        context.checker.check(onConflict <= IGNORE && onConflict >= REPLACE,
+                executableElement, ProcessorErrors.INVALID_ON_CONFLICT_VALUE)
+
+        val returnTypeName = delegate.extractReturnType().typeName()
+        context.checker.check(
+                returnTypeName == TypeName.VOID || returnTypeName == TypeName.INT,
+                executableElement,
+                ProcessorErrors.UPDATE_METHODS_MUST_RETURN_VOID_OR_INT
+        )
+
+        val (entities, params) = delegate.extractParams(
+                missingParamError = ProcessorErrors
+                        .UPDATE_MISSING_PARAMS
+        )
+
+        return UpdateMethod(
+                element = delegate.executableElement,
+                name = delegate.executableElement.simpleName.toString(),
+                entities = entities,
+                onConflictStrategy = onConflict,
+                returnCount = returnTypeName == TypeName.INT,
+                parameters = params
+        )
+    }
+}
diff --git a/room/compiler/src/main/kotlin/android/arch/persistence/room/processor/cache/Cache.kt b/room/compiler/src/main/kotlin/android/arch/persistence/room/processor/cache/Cache.kt
new file mode 100644
index 0000000..37f6c6d
--- /dev/null
+++ b/room/compiler/src/main/kotlin/android/arch/persistence/room/processor/cache/Cache.kt
@@ -0,0 +1,70 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+@file:Suppress("AddVarianceModifier")
+
+package android.arch.persistence.room.processor.cache
+
+import android.arch.persistence.room.processor.FieldProcessor
+import android.arch.persistence.room.vo.EmbeddedField
+import android.arch.persistence.room.vo.Entity
+import android.arch.persistence.room.vo.Pojo
+import android.arch.persistence.room.vo.Warning
+import java.util.LinkedHashSet
+import javax.lang.model.element.Element
+import javax.lang.model.type.TypeMirror
+
+/**
+ * A cache key can be used to avoid re-processing elements.
+ * <p>
+ * Each context has a cache variable that uses the same backing storage as the Root Context but
+ * adds current adapters and warning suppression list to the key.
+ */
+class Cache(val parent: Cache?, val converters: LinkedHashSet<TypeMirror>,
+            val suppressedWarnings: Set<Warning>) {
+    val entities: Bucket<EntityKey, Entity> = Bucket(parent?.entities)
+    val pojos: Bucket<PojoKey, Pojo> = Bucket(parent?.pojos)
+
+    inner class Bucket<K, T>(source: Bucket<K, T>?) {
+        private val entries: MutableMap<FullKey<K>, T> = source?.entries ?: mutableMapOf()
+        fun get(key : K, calculate: () -> T): T {
+            val fullKey = FullKey(converters, suppressedWarnings, key)
+            return entries.getOrPut(fullKey, {
+                calculate()
+            })
+        }
+    }
+
+    /**
+     * Key for Entity cache
+     */
+    data class EntityKey(val element: Element)
+
+    /**
+     * Key for Pojo cache
+     */
+    data class PojoKey(val element: Element, val scope : FieldProcessor.BindingScope,
+                       val parent : EmbeddedField?)
+
+    /**
+     * Internal key representation with adapters & warnings included.
+     * <p>
+     * Converters are kept in a linked set since the order is important for the TypeAdapterStore.
+     */
+    private data class FullKey<T>(val converters: LinkedHashSet<TypeMirror>,
+                               val suppressedWarnings: Set<Warning>,
+                               val key: T)
+}
diff --git a/room/compiler/src/main/kotlin/android/arch/persistence/room/solver/CodeGenScope.kt b/room/compiler/src/main/kotlin/android/arch/persistence/room/solver/CodeGenScope.kt
new file mode 100644
index 0000000..64e4f48
--- /dev/null
+++ b/room/compiler/src/main/kotlin/android/arch/persistence/room/solver/CodeGenScope.kt
@@ -0,0 +1,70 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.arch.persistence.room.solver
+
+import com.google.common.annotations.VisibleForTesting
+import com.squareup.javapoet.CodeBlock
+import android.arch.persistence.room.writer.ClassWriter
+/**
+ * Defines a code generation scope where we can provide temporary variables, global variables etc
+ */
+class CodeGenScope(val writer : ClassWriter) {
+    private var tmpVarIndices = mutableMapOf<String, Int>()
+    private var builder : CodeBlock.Builder? = null
+    companion object {
+        const val TMP_VAR_DEFAULT_PREFIX = "_tmp"
+        const val CLASS_PROPERTY_PREFIX = "__"
+        @VisibleForTesting
+        fun _tmpVar(index:Int) = _tmpVar(TMP_VAR_DEFAULT_PREFIX, index)
+        fun _tmpVar(prefix : String, index:Int) = "$prefix${if(index == 0) "" else "_$index"}"
+    }
+
+    fun builder() : CodeBlock.Builder {
+        if (builder == null) {
+            builder = CodeBlock.builder()
+        }
+        return builder!!
+    }
+
+    fun getTmpVar() : String {
+        return getTmpVar(TMP_VAR_DEFAULT_PREFIX)
+    }
+
+    fun getTmpVar(prefix : String) : String {
+        if (!prefix.startsWith("_")) {
+            throw IllegalArgumentException("tmp variable prefixes should start with _")
+        }
+        if (prefix.startsWith(CLASS_PROPERTY_PREFIX)) {
+            throw IllegalArgumentException("cannot use $CLASS_PROPERTY_PREFIX for tmp variables")
+        }
+        val index = tmpVarIndices.getOrElse(prefix) { 0 }
+        val result = _tmpVar(prefix, index)
+        tmpVarIndices.put(prefix, index + 1)
+        return result
+    }
+
+    fun generate() = builder().build().toString()
+
+    /**
+     * copies all variable indices but excludes generated code.
+     */
+    fun fork() : CodeGenScope {
+        val forked = CodeGenScope(writer)
+        forked.tmpVarIndices.putAll(tmpVarIndices)
+        return forked
+    }
+}
diff --git a/room/compiler/src/main/kotlin/android/arch/persistence/room/solver/TypeAdapterStore.kt b/room/compiler/src/main/kotlin/android/arch/persistence/room/solver/TypeAdapterStore.kt
new file mode 100644
index 0000000..b966818
--- /dev/null
+++ b/room/compiler/src/main/kotlin/android/arch/persistence/room/solver/TypeAdapterStore.kt
@@ -0,0 +1,492 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.arch.persistence.room.solver
+
+import android.arch.persistence.room.Entity
+import android.arch.persistence.room.ext.AndroidTypeNames
+import android.arch.persistence.room.ext.LifecyclesTypeNames
+import android.arch.persistence.room.ext.RoomRxJava2TypeNames
+import android.arch.persistence.room.ext.RxJava2TypeNames
+import android.arch.persistence.room.ext.hasAnnotation
+import android.arch.persistence.room.parser.ParsedQuery
+import android.arch.persistence.room.parser.SQLTypeAffinity
+import android.arch.persistence.room.processor.Context
+import android.arch.persistence.room.processor.EntityProcessor
+import android.arch.persistence.room.processor.FieldProcessor
+import android.arch.persistence.room.processor.PojoProcessor
+import android.arch.persistence.room.processor.ProcessorErrors
+import android.arch.persistence.room.solver.query.parameter.ArrayQueryParameterAdapter
+import android.arch.persistence.room.solver.query.parameter.BasicQueryParameterAdapter
+import android.arch.persistence.room.solver.query.parameter.CollectionQueryParameterAdapter
+import android.arch.persistence.room.solver.query.parameter.QueryParameterAdapter
+import android.arch.persistence.room.solver.query.result.ArrayQueryResultAdapter
+import android.arch.persistence.room.solver.query.result.CursorQueryResultBinder
+import android.arch.persistence.room.solver.query.result.EntityRowAdapter
+import android.arch.persistence.room.solver.query.result.FlowableQueryResultBinder
+import android.arch.persistence.room.solver.query.result.InstantQueryResultBinder
+import android.arch.persistence.room.solver.query.result.ListQueryResultAdapter
+import android.arch.persistence.room.solver.query.result.LiveDataQueryResultBinder
+import android.arch.persistence.room.solver.query.result.PojoRowAdapter
+import android.arch.persistence.room.solver.query.result.QueryResultAdapter
+import android.arch.persistence.room.solver.query.result.QueryResultBinder
+import android.arch.persistence.room.solver.query.result.RowAdapter
+import android.arch.persistence.room.solver.query.result.SingleColumnRowAdapter
+import android.arch.persistence.room.solver.query.result.SingleEntityQueryResultAdapter
+import android.arch.persistence.room.solver.types.BoxedBooleanToBoxedIntConverter
+import android.arch.persistence.room.solver.types.BoxedPrimitiveColumnTypeAdapter
+import android.arch.persistence.room.solver.types.ByteArrayColumnTypeAdapter
+import android.arch.persistence.room.solver.types.ColumnTypeAdapter
+import android.arch.persistence.room.solver.types.CompositeAdapter
+import android.arch.persistence.room.solver.types.CompositeTypeConverter
+import android.arch.persistence.room.solver.types.CursorValueReader
+import android.arch.persistence.room.solver.types.NoOpConverter
+import android.arch.persistence.room.solver.types.PrimitiveBooleanToIntConverter
+import android.arch.persistence.room.solver.types.PrimitiveColumnTypeAdapter
+import android.arch.persistence.room.solver.types.StatementValueBinder
+import android.arch.persistence.room.solver.types.StringColumnTypeAdapter
+import android.arch.persistence.room.solver.types.TypeConverter
+import com.google.auto.common.MoreElements
+import com.google.auto.common.MoreTypes
+import com.google.common.annotations.VisibleForTesting
+import com.squareup.javapoet.TypeName
+import java.util.LinkedList
+import javax.lang.model.type.ArrayType
+import javax.lang.model.type.DeclaredType
+import javax.lang.model.type.TypeKind
+import javax.lang.model.type.TypeMirror
+import javax.lang.model.util.Types
+
+@Suppress("PLATFORM_CLASS_MAPPED_TO_KOTLIN")
+/**
+ * Holds all type adapters and can create on demand composite type adapters to convert a type into a
+ * database column.
+ */
+class TypeAdapterStore private constructor(val context: Context,
+                                           /**
+                                            * first type adapter has the highest priority
+                                            */
+                                           private val columnTypeAdapters: List<ColumnTypeAdapter>,
+                                           /**
+                                            * first converter has the highest priority
+                                            */
+                                           private val typeConverters: List<TypeConverter>) {
+
+
+    companion object {
+        fun copy(context : Context, store : TypeAdapterStore) : TypeAdapterStore {
+            return TypeAdapterStore(context = context,
+                    columnTypeAdapters = store.columnTypeAdapters,
+                    typeConverters = store.typeConverters)
+        }
+
+        fun create(context: Context, @VisibleForTesting vararg extras: Any) : TypeAdapterStore {
+            val adapters = arrayListOf<ColumnTypeAdapter>()
+            val converters = arrayListOf<TypeConverter>()
+
+            fun addAny(extra: Any?) {
+                when (extra) {
+                    is TypeConverter -> converters.add(extra)
+                    is ColumnTypeAdapter -> adapters.add(extra)
+                    is List<*> -> extra.forEach(::addAny)
+                    else -> throw IllegalArgumentException("unknown extra $extra")
+                }
+            }
+
+            extras.forEach(::addAny)
+            fun addTypeConverter(converter: TypeConverter) {
+                converters.add(converter)
+            }
+
+            fun addColumnAdapter(adapter: ColumnTypeAdapter) {
+                adapters.add(adapter)
+            }
+
+            val primitives = PrimitiveColumnTypeAdapter
+                    .createPrimitiveAdapters(context.processingEnv)
+            primitives.forEach(::addColumnAdapter)
+            BoxedPrimitiveColumnTypeAdapter
+                    .createBoxedPrimitiveAdapters(context.processingEnv, primitives)
+                    .forEach(::addColumnAdapter)
+            addColumnAdapter(StringColumnTypeAdapter(context.processingEnv))
+            addColumnAdapter(ByteArrayColumnTypeAdapter(context.processingEnv))
+            PrimitiveBooleanToIntConverter.create(context.processingEnv).forEach(::addTypeConverter)
+            BoxedBooleanToBoxedIntConverter.create(context.processingEnv)
+                    .forEach(::addTypeConverter)
+            return TypeAdapterStore(context = context, columnTypeAdapters = adapters,
+                    typeConverters = converters)
+        }
+    }
+
+    val hasRxJava2Artifact by lazy {
+        context.processingEnv.elementUtils
+                .getTypeElement(RoomRxJava2TypeNames.RX_ROOM.toString()) != null
+    }
+
+    // type mirrors that be converted into columns w/o an extra converter
+    private val knownColumnTypeMirrors by lazy {
+        columnTypeAdapters.map { it.out }
+    }
+
+    private val liveDataTypeMirror: TypeMirror? by lazy {
+        context.processingEnv.elementUtils
+                .getTypeElement(LifecyclesTypeNames.LIVE_DATA.toString())?.asType()
+    }
+
+    private val flowableTypeMirror: TypeMirror? by lazy {
+        context.processingEnv.elementUtils
+                .getTypeElement(RxJava2TypeNames.FLOWABLE.toString())?.asType()
+    }
+
+    /**
+     * Searches 1 way to bind a value into a statement.
+     */
+    fun findStatementValueBinder(input : TypeMirror, affinity: SQLTypeAffinity?)
+            : StatementValueBinder? {
+        if (input.kind == TypeKind.ERROR) {
+            return null
+        }
+        val adapter = findDirectAdapterFor(input, affinity)
+        if (adapter != null) {
+            return adapter
+        }
+        val targetTypes = targetTypeMirrorsFor(affinity)
+        val binder = findTypeConverter(input, targetTypes) ?: return null
+        return CompositeAdapter(input, getAllColumnAdapters(binder.to).first(), binder, null)
+    }
+
+    /**
+     * Returns which entities targets the given affinity.
+     */
+    private fun targetTypeMirrorsFor(affinity: SQLTypeAffinity?) : List<TypeMirror> {
+        val specifiedTargets = affinity?.getTypeMirrors(context.processingEnv)
+        return if(specifiedTargets == null || specifiedTargets.isEmpty()) {
+            knownColumnTypeMirrors
+        } else {
+            specifiedTargets
+        }
+    }
+
+    /**
+     * Searches 1 way to read it from cursor
+     */
+    fun findCursorValueReader(output: TypeMirror, affinity: SQLTypeAffinity?) : CursorValueReader? {
+        if (output.kind == TypeKind.ERROR) {
+            return null
+        }
+        val adapter = findColumnTypeAdapter(output, affinity)
+        if (adapter != null) {
+            // two way is better
+            return adapter
+        }
+        // we could not find a two way version, search for anything
+        val targetTypes = targetTypeMirrorsFor(affinity)
+        val converter = findTypeConverter(targetTypes, output) ?: return null
+        return CompositeAdapter(output,
+                getAllColumnAdapters(converter.from).first(), null, converter)
+    }
+
+    /**
+     * Tries to reverse the converter going through the same nodes, if possible.
+     */
+    @VisibleForTesting
+    fun reverse(converter : TypeConverter) : TypeConverter? {
+        return when(converter) {
+            is NoOpConverter -> converter
+            is CompositeTypeConverter ->  {
+                val r1 = reverse(converter.conv1) ?: return null
+                val r2 = reverse(converter.conv2) ?: return null
+                CompositeTypeConverter(r2, r1)
+            }
+            else -> {
+                val types = context.processingEnv.typeUtils
+                typeConverters.firstOrNull {
+                    types.isSameType(it.from, converter.to) && types
+                            .isSameType(it.to, converter.from)
+                }
+            }
+        }
+    }
+
+    /**
+     * Finds a two way converter, if you need 1 way, use findStatementValueBinder or
+     * findCursorValueReader.
+     */
+    fun findColumnTypeAdapter(out: TypeMirror, affinity: SQLTypeAffinity?)
+            : ColumnTypeAdapter? {
+        if (out.kind == TypeKind.ERROR) {
+            return null
+        }
+        val adapter = findDirectAdapterFor(out, affinity)
+        if (adapter != null) {
+            return adapter
+        }
+        val targetTypes = targetTypeMirrorsFor(affinity)
+        val intoStatement = findTypeConverter(out, targetTypes) ?: return null
+        // ok found a converter, try the reverse now
+        val fromCursor = reverse(intoStatement) ?: findTypeConverter(intoStatement.to, out)
+                ?: return null
+        return CompositeAdapter(out, getAllColumnAdapters(intoStatement.to).first(), intoStatement,
+                fromCursor)
+    }
+
+    private fun findDirectAdapterFor(out: TypeMirror, affinity: SQLTypeAffinity?)
+            : ColumnTypeAdapter? {
+        val adapter = getAllColumnAdapters(out).firstOrNull {
+            affinity == null || it.typeAffinity == affinity
+        }
+        return adapter
+    }
+
+    fun findTypeConverter(input: TypeMirror, output: TypeMirror): TypeConverter? {
+        return findTypeConverter(listOf(input), listOf(output))
+    }
+
+    @VisibleForTesting
+    fun isLiveData(declared: DeclaredType): Boolean {
+        if (liveDataTypeMirror == null) {
+            return false
+        }
+        val erasure = context.processingEnv.typeUtils.erasure(declared)
+        return context.processingEnv.typeUtils.isAssignable(liveDataTypeMirror, erasure)
+    }
+
+    @VisibleForTesting
+    fun isRxJava2Publisher(declared: DeclaredType): Boolean {
+        if (flowableTypeMirror == null) {
+            return false
+        }
+        val erasure = context.processingEnv.typeUtils.erasure(declared)
+        val match = context.processingEnv.typeUtils.isAssignable(flowableTypeMirror, erasure)
+        if (match && !hasRxJava2Artifact) {
+            context.logger.e(ProcessorErrors.MISSING_ROOM_RXJAVA2_ARTIFACT)
+        }
+        return match
+    }
+
+    fun findQueryResultBinder(typeMirror: TypeMirror, query: ParsedQuery): QueryResultBinder {
+        return if (typeMirror.kind == TypeKind.DECLARED) {
+            val declared = MoreTypes.asDeclared(typeMirror)
+            if (declared.typeArguments.isEmpty()) {
+                if (TypeName.get(declared) == AndroidTypeNames.CURSOR) {
+                    return CursorQueryResultBinder()
+                }
+                InstantQueryResultBinder(findQueryResultAdapter(typeMirror, query))
+            } else {
+                if (isLiveData(declared)) {
+                    val liveDataTypeArg = declared.typeArguments.first()
+                    LiveDataQueryResultBinder(liveDataTypeArg, query.tables.map { it.name },
+                            findQueryResultAdapter(liveDataTypeArg, query))
+                } else if (isRxJava2Publisher(declared)) {
+                    val typeArg = declared.typeArguments.first()
+                    FlowableQueryResultBinder(typeArg, query.tables.map { it.name },
+                            findQueryResultAdapter(typeArg, query))
+                } else {
+                    InstantQueryResultBinder(findQueryResultAdapter(typeMirror, query))
+                }
+            }
+        } else {
+            InstantQueryResultBinder(findQueryResultAdapter(typeMirror, query))
+        }
+    }
+
+    private fun findQueryResultAdapter(typeMirror: TypeMirror, query: ParsedQuery)
+            : QueryResultAdapter? {
+        if (typeMirror.kind == TypeKind.ERROR) {
+            return null
+        }
+        if (typeMirror.kind == TypeKind.DECLARED) {
+            val declared = MoreTypes.asDeclared(typeMirror)
+            if (declared.typeArguments.isEmpty()) {
+                val rowAdapter = findRowAdapter(typeMirror, query) ?: return null
+                return SingleEntityQueryResultAdapter(rowAdapter)
+            }
+            if (MoreTypes.isTypeOf(java.util.List::class.java, typeMirror)) {
+                val typeArg = declared.typeArguments.first()
+                val rowAdapter = findRowAdapter(typeArg, query) ?: return null
+                return ListQueryResultAdapter(rowAdapter)
+            }
+            return null
+        } else if (typeMirror is ArrayType && typeMirror.componentType.kind != TypeKind.BYTE) {
+            val rowAdapter =
+                    findRowAdapter(typeMirror.componentType, query) ?: return null
+            return ArrayQueryResultAdapter(rowAdapter)
+        } else {
+            val rowAdapter = findRowAdapter(typeMirror, query) ?: return null
+            return SingleEntityQueryResultAdapter(rowAdapter)
+        }
+    }
+
+    /**
+     * Find a converter from cursor to the given type mirror.
+     * If there is information about the query result, we try to use it to accept *any* POJO.
+     */
+    @VisibleForTesting
+    fun findRowAdapter(typeMirror: TypeMirror, query: ParsedQuery): RowAdapter? {
+        if (typeMirror.kind == TypeKind.ERROR) {
+            return null
+        }
+        if (typeMirror.kind == TypeKind.DECLARED) {
+            val declared = MoreTypes.asDeclared(typeMirror)
+            if (declared.typeArguments.isNotEmpty()) {
+                // TODO one day support this
+                return null
+            }
+            val resultInfo = query.resultInfo
+
+            val (rowAdapter, rowAdapterLogs) = if (resultInfo != null && query.errors.isEmpty()
+                    && resultInfo.error == null) {
+                // if result info is not null, first try a pojo row adapter
+                context.collectLogs { subContext ->
+                    val pojo = PojoProcessor(
+                            baseContext = subContext,
+                            element = MoreTypes.asTypeElement(typeMirror),
+                            bindingScope = FieldProcessor.BindingScope.READ_FROM_CURSOR,
+                            parent = null
+                    ).process()
+                    PojoRowAdapter(
+                            context = subContext,
+                            info = resultInfo,
+                            pojo = pojo,
+                            out = typeMirror)
+                }
+            } else {
+                Pair(null, null)
+            }
+
+            if (rowAdapter == null && query.resultInfo == null) {
+                // we don't know what query returns. Check for entity.
+                val asElement = MoreTypes.asElement(typeMirror)
+                if (asElement.hasAnnotation(Entity::class)) {
+                    return EntityRowAdapter(EntityProcessor(context,
+                            MoreElements.asType(asElement)).process())
+                }
+            }
+
+            if (rowAdapter != null && !(rowAdapterLogs?.hasErrors() ?: false)) {
+                rowAdapterLogs?.writeTo(context.processingEnv)
+                return rowAdapter
+            }
+
+            if ((resultInfo?.columns?.size ?: 1) == 1) {
+                val singleColumn = findCursorValueReader(typeMirror,
+                        resultInfo?.columns?.get(0)?.type)
+                if (singleColumn != null) {
+                    return SingleColumnRowAdapter(singleColumn)
+                }
+            }
+            // if we tried, return its errors
+            if (rowAdapter != null) {
+                rowAdapterLogs?.writeTo(context.processingEnv)
+                return rowAdapter
+            }
+            return null
+        } else {
+            val singleColumn = findCursorValueReader(typeMirror, null) ?: return null
+            return SingleColumnRowAdapter(singleColumn)
+        }
+    }
+
+    fun findQueryParameterAdapter(typeMirror: TypeMirror): QueryParameterAdapter? {
+        if (MoreTypes.isType(typeMirror)
+                && (MoreTypes.isTypeOf(java.util.List::class.java, typeMirror)
+                || MoreTypes.isTypeOf(java.util.Set::class.java, typeMirror))) {
+            val declared = MoreTypes.asDeclared(typeMirror)
+            val binder = findStatementValueBinder(declared.typeArguments.first(),
+                    null) ?: return null
+            return CollectionQueryParameterAdapter(binder)
+        } else if (typeMirror is ArrayType && typeMirror.componentType.kind != TypeKind.BYTE) {
+            val component = typeMirror.componentType
+            val binder = findStatementValueBinder(component, null) ?: return null
+            return ArrayQueryParameterAdapter(binder)
+        } else {
+            val binder = findStatementValueBinder(typeMirror, null) ?: return null
+            return BasicQueryParameterAdapter(binder)
+        }
+    }
+
+    private fun findTypeConverter(input: TypeMirror, outputs: List<TypeMirror>): TypeConverter? {
+        return findTypeConverter(listOf(input), outputs)
+    }
+
+    private fun findTypeConverter(input: List<TypeMirror>, output : TypeMirror): TypeConverter? {
+        return findTypeConverter(input, listOf(output))
+    }
+
+    private fun findTypeConverter(inputs: List<TypeMirror>, outputs: List<TypeMirror>)
+            : TypeConverter? {
+        if (inputs.isEmpty()) {
+            return null
+        }
+        val types = context.processingEnv.typeUtils
+        inputs.forEach { input ->
+            if (outputs.any { output -> types.isSameType(input, output) }) {
+                return NoOpConverter(input)
+            }
+        }
+
+        val excludes = arrayListOf<TypeMirror>()
+
+        val queue = LinkedList<TypeConverter>()
+        fun exactMatch(candidates: List<TypeConverter>, outputs: List<TypeMirror>, types: Types)
+                : TypeConverter? {
+            return candidates.firstOrNull {
+                outputs.any { output -> types.isSameType(output, it.to) }
+            }
+        }
+        inputs.forEach { input ->
+            val candidates = getAllTypeConverters(input, excludes)
+            val match = exactMatch(candidates, outputs, types)
+            if (match != null) {
+                return match
+            }
+            candidates.forEach {
+                excludes.add(it.to)
+                queue.add(it)
+            }
+        }
+        excludes.addAll(inputs)
+        while (queue.isNotEmpty()) {
+            val prev = queue.pop()
+            val from = prev.to
+            val candidates = getAllTypeConverters(from, excludes)
+            val match = exactMatch(candidates, outputs, types)
+            if (match != null) {
+                return CompositeTypeConverter(prev, match)
+            }
+            candidates.forEach {
+                excludes.add(it.to)
+                queue.add(CompositeTypeConverter(prev, it))
+            }
+        }
+        return null
+    }
+
+    private fun getAllColumnAdapters(input: TypeMirror): List<ColumnTypeAdapter> {
+        return columnTypeAdapters.filter {
+            context.processingEnv.typeUtils.isSameType(input, it.out)
+        }
+    }
+
+    private fun getAllTypeConverters(input: TypeMirror, excludes: List<TypeMirror>):
+            List<TypeConverter> {
+        val types = context.processingEnv.typeUtils
+        return typeConverters.filter { converter ->
+            types.isSameType(input, converter.from) &&
+                    !excludes.any { types.isSameType(it, converter.to) }
+        }
+    }
+}
diff --git a/room/compiler/src/main/kotlin/android/arch/persistence/room/solver/query/parameter/ArrayQueryParameterAdapter.kt b/room/compiler/src/main/kotlin/android/arch/persistence/room/solver/query/parameter/ArrayQueryParameterAdapter.kt
new file mode 100644
index 0000000..9cafb23
--- /dev/null
+++ b/room/compiler/src/main/kotlin/android/arch/persistence/room/solver/query/parameter/ArrayQueryParameterAdapter.kt
@@ -0,0 +1,48 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.arch.persistence.room.solver.query.parameter
+
+import android.arch.persistence.room.ext.L
+import android.arch.persistence.room.ext.T
+import android.arch.persistence.room.ext.typeName
+import android.arch.persistence.room.solver.CodeGenScope
+import android.arch.persistence.room.solver.types.StatementValueBinder
+import com.squareup.javapoet.TypeName
+
+/**
+ * Binds ARRAY(T) (e.g. int[]) into String[] args of a query.
+ */
+class ArrayQueryParameterAdapter(val bindAdapter : StatementValueBinder)
+            : QueryParameterAdapter(true) {
+    override fun bindToStmt(inputVarName: String, stmtVarName: String, startIndexVarName: String,
+                            scope: CodeGenScope) {
+        scope.builder().apply {
+            val itrVar = scope.getTmpVar("_item")
+            beginControlFlow("for ($T $L : $L)", bindAdapter.typeMirror().typeName(), itrVar,
+                    inputVarName).apply {
+                        bindAdapter.bindToStmt(stmtVarName, startIndexVarName, itrVar, scope)
+                        addStatement("$L ++", startIndexVarName)
+            }
+            endControlFlow()
+        }
+    }
+
+    override fun getArgCount(inputVarName: String, outputVarName : String, scope: CodeGenScope) {
+        scope.builder()
+                .addStatement("final $T $L = $L.length", TypeName.INT, outputVarName, inputVarName)
+    }
+}
diff --git a/room/compiler/src/main/kotlin/android/arch/persistence/room/solver/query/parameter/BasicQueryParameterAdapter.kt b/room/compiler/src/main/kotlin/android/arch/persistence/room/solver/query/parameter/BasicQueryParameterAdapter.kt
new file mode 100644
index 0000000..11521f2
--- /dev/null
+++ b/room/compiler/src/main/kotlin/android/arch/persistence/room/solver/query/parameter/BasicQueryParameterAdapter.kt
@@ -0,0 +1,38 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.arch.persistence.room.solver.query.parameter
+
+import android.arch.persistence.room.solver.CodeGenScope
+import android.arch.persistence.room.solver.types.StatementValueBinder
+
+/**
+ * Knows how to convert a query parameter into arguments
+ */
+class BasicQueryParameterAdapter(val bindAdapter : StatementValueBinder)
+            : QueryParameterAdapter(false) {
+    override fun bindToStmt(inputVarName: String, stmtVarName: String, startIndexVarName: String,
+                            scope: CodeGenScope) {
+        scope.builder().apply {
+            bindAdapter.bindToStmt(stmtVarName, startIndexVarName, inputVarName, scope)
+        }
+    }
+
+    override fun getArgCount(inputVarName: String, outputVarName : String, scope: CodeGenScope) {
+        throw UnsupportedOperationException("should not call getArgCount on basic adapters." +
+                "It is always one.")
+    }
+}
diff --git a/room/compiler/src/main/kotlin/android/arch/persistence/room/solver/query/parameter/CollectionQueryParameterAdapter.kt b/room/compiler/src/main/kotlin/android/arch/persistence/room/solver/query/parameter/CollectionQueryParameterAdapter.kt
new file mode 100644
index 0000000..7e0b206
--- /dev/null
+++ b/room/compiler/src/main/kotlin/android/arch/persistence/room/solver/query/parameter/CollectionQueryParameterAdapter.kt
@@ -0,0 +1,48 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.arch.persistence.room.solver.query.parameter
+
+import android.arch.persistence.room.ext.L
+import android.arch.persistence.room.ext.T
+import android.arch.persistence.room.ext.typeName
+import android.arch.persistence.room.solver.CodeGenScope
+import android.arch.persistence.room.solver.types.StatementValueBinder
+import com.squareup.javapoet.TypeName
+
+/**
+ * Binds Collection<T> (e.g. List<T>) into String[] query args.
+ */
+class CollectionQueryParameterAdapter(val bindAdapter : StatementValueBinder)
+            : QueryParameterAdapter(true) {
+    override fun bindToStmt(inputVarName: String, stmtVarName: String, startIndexVarName: String,
+                            scope: CodeGenScope) {
+        scope.builder().apply {
+            val itrVar = scope.getTmpVar("_item")
+            beginControlFlow("for ($T $L : $L)", bindAdapter.typeMirror().typeName(), itrVar,
+                    inputVarName).apply {
+                        bindAdapter.bindToStmt(stmtVarName, startIndexVarName, itrVar, scope)
+                        addStatement("$L ++", startIndexVarName)
+                    }
+            endControlFlow()
+        }
+    }
+
+    override fun getArgCount(inputVarName: String, outputVarName : String, scope: CodeGenScope) {
+        scope.builder()
+                .addStatement("final $T $L = $L.size()", TypeName.INT, outputVarName, inputVarName)
+    }
+}
diff --git a/room/compiler/src/main/kotlin/android/arch/persistence/room/solver/query/parameter/QueryParameterAdapter.kt b/room/compiler/src/main/kotlin/android/arch/persistence/room/solver/query/parameter/QueryParameterAdapter.kt
new file mode 100644
index 0000000..b93659e
--- /dev/null
+++ b/room/compiler/src/main/kotlin/android/arch/persistence/room/solver/query/parameter/QueryParameterAdapter.kt
@@ -0,0 +1,35 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.arch.persistence.room.solver.query.parameter
+
+import android.arch.persistence.room.solver.CodeGenScope
+
+/**
+ * Knows how to convert a query parameter into query arguments.
+ */
+abstract class QueryParameterAdapter(val isMultiple: Boolean) {
+    /**
+     * Must bind the value into the statement at the given index.
+     */
+    abstract fun bindToStmt(inputVarName: String, stmtVarName: String, startIndexVarName: String,
+                         scope: CodeGenScope)
+
+    /**
+     * Should declare and set the given value with the count
+     */
+    abstract fun getArgCount(inputVarName: String, outputVarName : String, scope : CodeGenScope)
+}
diff --git a/room/compiler/src/main/kotlin/android/arch/persistence/room/solver/query/result/ArrayQueryResultAdapter.kt b/room/compiler/src/main/kotlin/android/arch/persistence/room/solver/query/result/ArrayQueryResultAdapter.kt
new file mode 100644
index 0000000..e8330bc
--- /dev/null
+++ b/room/compiler/src/main/kotlin/android/arch/persistence/room/solver/query/result/ArrayQueryResultAdapter.kt
@@ -0,0 +1,48 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.arch.persistence.room.solver.query.result
+
+import android.arch.persistence.room.ext.L
+import android.arch.persistence.room.ext.T
+import android.arch.persistence.room.ext.typeName
+import android.arch.persistence.room.solver.CodeGenScope
+import com.squareup.javapoet.ArrayTypeName
+import com.squareup.javapoet.TypeName
+
+class ArrayQueryResultAdapter(rowAdapter: RowAdapter) : QueryResultAdapter(rowAdapter) {
+    val type = rowAdapter.out
+    override fun convert(outVarName: String, cursorVarName: String, scope: CodeGenScope) {
+        scope.builder().apply {
+            rowAdapter?.onCursorReady(cursorVarName, scope)
+
+            val arrayType = ArrayTypeName.of(type.typeName())
+            addStatement("final $T $L = new $T[$L.getCount()]",
+                    arrayType, outVarName, type.typeName(), cursorVarName)
+            val tmpVarName = scope.getTmpVar("_item")
+            val indexVar = scope.getTmpVar("_index")
+            addStatement("$T $L = 0", TypeName.INT, indexVar)
+            beginControlFlow("while($L.moveToNext())", cursorVarName).apply {
+                addStatement("final $T $L", type.typeName(), tmpVarName)
+                rowAdapter?.convert(tmpVarName, cursorVarName, scope)
+                addStatement("$L[$L] = $L", outVarName, indexVar, tmpVarName)
+                addStatement("$L ++", indexVar)
+            }
+            endControlFlow()
+            rowAdapter?.onCursorFinished()?.invoke(scope)
+        }
+    }
+}
diff --git a/room/compiler/src/main/kotlin/android/arch/persistence/room/solver/query/result/BaseObservableQueryResultBinder.kt b/room/compiler/src/main/kotlin/android/arch/persistence/room/solver/query/result/BaseObservableQueryResultBinder.kt
new file mode 100644
index 0000000..7872c33
--- /dev/null
+++ b/room/compiler/src/main/kotlin/android/arch/persistence/room/solver/query/result/BaseObservableQueryResultBinder.kt
@@ -0,0 +1,63 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.arch.persistence.room.solver.query.result
+
+import android.arch.persistence.room.ext.AndroidTypeNames
+import android.arch.persistence.room.ext.L
+import android.arch.persistence.room.ext.N
+import android.arch.persistence.room.ext.T
+import android.arch.persistence.room.solver.CodeGenScope
+import android.arch.persistence.room.writer.DaoWriter
+import com.squareup.javapoet.MethodSpec
+import javax.lang.model.element.Modifier
+
+/**
+ * Base class for query result binders that observe the database. It includes common functionality
+ * like creating a finalizer to release the query or creating the actual adapter call code.
+ */
+abstract class BaseObservableQueryResultBinder(adapter: QueryResultAdapter?)
+    : QueryResultBinder(adapter) {
+
+    protected fun createFinalizeMethod(roomSQLiteQueryVar: String): MethodSpec {
+        return MethodSpec.methodBuilder("finalize").apply {
+            addModifiers(Modifier.PROTECTED)
+            addAnnotation(Override::class.java)
+            addStatement("$L.release()", roomSQLiteQueryVar)
+        }.build()
+    }
+
+    protected fun createRunQueryAndReturnStatements(builder: MethodSpec.Builder,
+                                                    roomSQLiteQueryVar: String,
+                                                    scope: CodeGenScope) {
+        val outVar = scope.getTmpVar("_result")
+        val cursorVar = scope.getTmpVar("_cursor")
+        builder.apply {
+            addStatement("final $T $L = $N.query($L)", AndroidTypeNames.CURSOR, cursorVar,
+                    DaoWriter.dbField, roomSQLiteQueryVar)
+            beginControlFlow("try").apply {
+                val adapterScope = scope.fork()
+                adapter?.convert(outVar, cursorVar, adapterScope)
+                addCode(adapterScope.builder().build())
+                addStatement("return $L", outVar)
+            }
+            nextControlFlow("finally").apply {
+                addStatement("$L.close()", cursorVar)
+            }
+            endControlFlow()
+        }
+    }
+}
diff --git a/room/compiler/src/main/kotlin/android/arch/persistence/room/solver/query/result/CursorQueryResultBinder.kt b/room/compiler/src/main/kotlin/android/arch/persistence/room/solver/query/result/CursorQueryResultBinder.kt
new file mode 100644
index 0000000..cd8ea74
--- /dev/null
+++ b/room/compiler/src/main/kotlin/android/arch/persistence/room/solver/query/result/CursorQueryResultBinder.kt
@@ -0,0 +1,38 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.arch.persistence.room.solver.query.result
+
+import android.arch.persistence.room.ext.L
+import android.arch.persistence.room.ext.N
+import android.arch.persistence.room.solver.CodeGenScope
+import android.arch.persistence.room.writer.DaoWriter
+import com.squareup.javapoet.FieldSpec
+
+class CursorQueryResultBinder : QueryResultBinder(NO_OP_RESULT_ADAPTER) {
+    override fun convertAndReturn(roomSQLiteQueryVar: String, dbField: FieldSpec,
+                                  scope: CodeGenScope) {
+        scope.builder().apply {
+            addStatement("return $N.query($L)", DaoWriter.dbField, roomSQLiteQueryVar)
+        }
+    }
+    companion object {
+        private val NO_OP_RESULT_ADAPTER = object : QueryResultAdapter(null) {
+            override fun convert(outVarName: String, cursorVarName: String, scope: CodeGenScope) {
+            }
+        }
+    }
+}
diff --git a/room/compiler/src/main/kotlin/android/arch/persistence/room/solver/query/result/EntityRowAdapter.kt b/room/compiler/src/main/kotlin/android/arch/persistence/room/solver/query/result/EntityRowAdapter.kt
new file mode 100644
index 0000000..c97f69e
--- /dev/null
+++ b/room/compiler/src/main/kotlin/android/arch/persistence/room/solver/query/result/EntityRowAdapter.kt
@@ -0,0 +1,36 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.arch.persistence.room.solver.query.result
+
+import android.arch.persistence.room.ext.L
+import android.arch.persistence.room.ext.N
+import android.arch.persistence.room.solver.CodeGenScope
+import android.arch.persistence.room.vo.Entity
+import android.arch.persistence.room.writer.EntityCursorConverterWriter
+import com.squareup.javapoet.MethodSpec
+
+class EntityRowAdapter(val entity: Entity) : RowAdapter(entity.type) {
+    lateinit var methodSpec: MethodSpec
+    override fun onCursorReady(cursorVarName: String, scope: CodeGenScope) {
+        methodSpec = scope.writer.getOrCreateMethod(EntityCursorConverterWriter(entity))
+    }
+
+    override fun convert(outVarName: String, cursorVarName: String, scope: CodeGenScope) {
+        scope.builder()
+                .addStatement("$L = $N($L)", outVarName, methodSpec, cursorVarName)
+    }
+}
diff --git a/room/compiler/src/main/kotlin/android/arch/persistence/room/solver/query/result/FlowableQueryResultBinder.kt b/room/compiler/src/main/kotlin/android/arch/persistence/room/solver/query/result/FlowableQueryResultBinder.kt
new file mode 100644
index 0000000..dbd66ce
--- /dev/null
+++ b/room/compiler/src/main/kotlin/android/arch/persistence/room/solver/query/result/FlowableQueryResultBinder.kt
@@ -0,0 +1,61 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.arch.persistence.room.solver.query.result
+
+import android.arch.persistence.room.ext.L
+import android.arch.persistence.room.ext.N
+import android.arch.persistence.room.ext.RoomRxJava2TypeNames
+import android.arch.persistence.room.ext.T
+import android.arch.persistence.room.ext.arrayTypeName
+import android.arch.persistence.room.ext.typeName
+import android.arch.persistence.room.solver.CodeGenScope
+import android.arch.persistence.room.writer.DaoWriter
+import com.squareup.javapoet.FieldSpec
+import com.squareup.javapoet.MethodSpec
+import com.squareup.javapoet.ParameterizedTypeName
+import com.squareup.javapoet.TypeSpec
+import javax.lang.model.element.Modifier
+import javax.lang.model.type.TypeMirror
+
+/**
+ * Binds the result as an RxJava2 Flowable.
+ */
+class FlowableQueryResultBinder(val typeArg: TypeMirror, val queryTableNames: List<String>,
+                                adapter: QueryResultAdapter?)
+    : BaseObservableQueryResultBinder(adapter) {
+    override fun convertAndReturn(roomSQLiteQueryVar: String, dbField: FieldSpec,
+                                  scope: CodeGenScope) {
+        val callableImpl = TypeSpec.anonymousClassBuilder("").apply {
+            val typeName = typeArg.typeName()
+            superclass(ParameterizedTypeName.get(java.util.concurrent.Callable::class.typeName(),
+                    typeName))
+            addMethod(MethodSpec.methodBuilder("call").apply {
+                returns(typeName)
+                addException(Exception::class.typeName())
+                addModifiers(Modifier.PUBLIC)
+                createRunQueryAndReturnStatements(this, roomSQLiteQueryVar, scope)
+            }.build())
+            addMethod(createFinalizeMethod(roomSQLiteQueryVar))
+        }.build()
+        scope.builder().apply {
+            val tableNamesList = queryTableNames.joinToString(",") { "\"$it\"" }
+            addStatement("return $T.createFlowable($N, new $T{$L}, $L)",
+                    RoomRxJava2TypeNames.RX_ROOM, DaoWriter.dbField,
+                    String::class.arrayTypeName(), tableNamesList, callableImpl)
+        }
+    }
+}
diff --git a/room/compiler/src/main/kotlin/android/arch/persistence/room/solver/query/result/InstantQueryResultBinder.kt b/room/compiler/src/main/kotlin/android/arch/persistence/room/solver/query/result/InstantQueryResultBinder.kt
new file mode 100644
index 0000000..ff15252
--- /dev/null
+++ b/room/compiler/src/main/kotlin/android/arch/persistence/room/solver/query/result/InstantQueryResultBinder.kt
@@ -0,0 +1,48 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package android.arch.persistence.room.solver.query.result
+
+import android.arch.persistence.room.ext.AndroidTypeNames
+import android.arch.persistence.room.ext.L
+import android.arch.persistence.room.ext.N
+import android.arch.persistence.room.ext.T
+import android.arch.persistence.room.solver.CodeGenScope
+import android.arch.persistence.room.writer.DaoWriter
+import com.squareup.javapoet.FieldSpec
+
+/**
+ * Instantly runs and returns the query.
+ */
+class InstantQueryResultBinder(adapter: QueryResultAdapter?) : QueryResultBinder(adapter) {
+    override fun convertAndReturn(roomSQLiteQueryVar : String, dbField: FieldSpec,
+                                  scope: CodeGenScope) {
+        scope.builder().apply {
+            val outVar = scope.getTmpVar("_result")
+            val cursorVar = scope.getTmpVar("_cursor")
+            addStatement("final $T $L = $N.query($L)", AndroidTypeNames.CURSOR, cursorVar,
+                    DaoWriter.dbField, roomSQLiteQueryVar)
+            beginControlFlow("try").apply {
+                adapter?.convert(outVar, cursorVar, scope)
+                addStatement("return $L", outVar)
+            }
+            nextControlFlow("finally").apply {
+                addStatement("$L.close()", cursorVar)
+                addStatement("$L.release()", roomSQLiteQueryVar)
+            }
+            endControlFlow()
+        }
+    }
+}
diff --git a/room/compiler/src/main/kotlin/android/arch/persistence/room/solver/query/result/ListQueryResultAdapter.kt b/room/compiler/src/main/kotlin/android/arch/persistence/room/solver/query/result/ListQueryResultAdapter.kt
new file mode 100644
index 0000000..b70ec5a
--- /dev/null
+++ b/room/compiler/src/main/kotlin/android/arch/persistence/room/solver/query/result/ListQueryResultAdapter.kt
@@ -0,0 +1,48 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.arch.persistence.room.solver.query.result
+
+import android.arch.persistence.room.ext.L
+import android.arch.persistence.room.ext.T
+import android.arch.persistence.room.ext.typeName
+import android.arch.persistence.room.solver.CodeGenScope
+import com.squareup.javapoet.ClassName
+import com.squareup.javapoet.ParameterizedTypeName
+import java.util.ArrayList
+
+class ListQueryResultAdapter(rowAdapter: RowAdapter) : QueryResultAdapter(rowAdapter) {
+    val type = rowAdapter.out
+    override fun convert(outVarName: String, cursorVarName: String, scope: CodeGenScope) {
+        scope.builder().apply {
+            rowAdapter?.onCursorReady(cursorVarName, scope)
+            val collectionType = ParameterizedTypeName
+                    .get(ClassName.get(List::class.java), type.typeName())
+            val arrayListType = ParameterizedTypeName
+                    .get(ClassName.get(ArrayList::class.java), type.typeName())
+            addStatement("final $T $L = new $T($L.getCount())",
+                    collectionType, outVarName, arrayListType, cursorVarName)
+            val tmpVarName = scope.getTmpVar("_item")
+            beginControlFlow("while($L.moveToNext())", cursorVarName).apply {
+                addStatement("final $T $L", type.typeName(), tmpVarName)
+                rowAdapter?.convert(tmpVarName, cursorVarName, scope)
+                addStatement("$L.add($L)", outVarName, tmpVarName)
+            }
+            endControlFlow()
+            rowAdapter?.onCursorFinished()?.invoke(scope)
+        }
+    }
+}
diff --git a/room/compiler/src/main/kotlin/android/arch/persistence/room/solver/query/result/LiveDataQueryResultBinder.kt b/room/compiler/src/main/kotlin/android/arch/persistence/room/solver/query/result/LiveDataQueryResultBinder.kt
new file mode 100644
index 0000000..4c9b0ca
--- /dev/null
+++ b/room/compiler/src/main/kotlin/android/arch/persistence/room/solver/query/result/LiveDataQueryResultBinder.kt
@@ -0,0 +1,103 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.arch.persistence.room.solver.query.result
+
+import android.arch.persistence.room.ext.L
+import android.arch.persistence.room.ext.LifecyclesTypeNames
+import android.arch.persistence.room.ext.N
+import android.arch.persistence.room.ext.RoomTypeNames
+import android.arch.persistence.room.ext.RoomTypeNames.INVALIDATION_OBSERVER
+import android.arch.persistence.room.ext.typeName
+import android.arch.persistence.room.solver.CodeGenScope
+import android.support.annotation.NonNull
+import com.squareup.javapoet.FieldSpec
+import com.squareup.javapoet.MethodSpec
+import com.squareup.javapoet.ParameterSpec
+import com.squareup.javapoet.ParameterizedTypeName
+import com.squareup.javapoet.TypeName
+import com.squareup.javapoet.TypeSpec
+import javax.lang.model.element.Modifier
+import javax.lang.model.type.TypeMirror
+
+/**
+ * Converts the query into a LiveData and returns it. No query is run until necessary.
+ */
+class LiveDataQueryResultBinder(val typeArg: TypeMirror, queryTableNames: List<String>,
+                                adapter: QueryResultAdapter?)
+    : BaseObservableQueryResultBinder(adapter) {
+    @Suppress("JoinDeclarationAndAssignment")
+    val tableNames = ((adapter?.accessedTableNames() ?: emptyList()) + queryTableNames).toSet()
+    override fun convertAndReturn(roomSQLiteQueryVar : String, dbField: FieldSpec,
+                                  scope: CodeGenScope) {
+        val typeName = typeArg.typeName()
+
+        val liveDataImpl = TypeSpec.anonymousClassBuilder("").apply {
+            superclass(ParameterizedTypeName.get(LifecyclesTypeNames.COMPUTABLE_LIVE_DATA,
+                    typeName))
+            val observerField = FieldSpec.builder(RoomTypeNames.INVALIDATION_OBSERVER,
+                    scope.getTmpVar("_observer"), Modifier.PRIVATE).build()
+            addField(observerField)
+            addMethod(createComputeMethod(
+                    observerField = observerField,
+                    typeName = typeName,
+                    roomSQLiteQueryVar = roomSQLiteQueryVar,
+                    dbField = dbField,
+                    scope = scope
+            ))
+            addMethod(createFinalizeMethod(roomSQLiteQueryVar))
+        }.build()
+        scope.builder().apply {
+            addStatement("return $L.getLiveData()", liveDataImpl)
+        }
+    }
+
+    private fun createComputeMethod(roomSQLiteQueryVar: String, typeName: TypeName,
+                                    observerField: FieldSpec, dbField: FieldSpec,
+                                    scope: CodeGenScope): MethodSpec {
+        return MethodSpec.methodBuilder("compute").apply {
+            addAnnotation(Override::class.java)
+            addModifiers(Modifier.PROTECTED)
+            returns(typeName)
+
+            beginControlFlow("if ($N == null)", observerField).apply {
+                addStatement("$N = $L", observerField, createAnonymousObserver())
+                addStatement("$N.getInvalidationTracker().addWeakObserver($N)",
+                        dbField, observerField)
+            }
+            endControlFlow()
+
+            createRunQueryAndReturnStatements(this, roomSQLiteQueryVar, scope)
+        }.build()
+    }
+
+    private fun createAnonymousObserver(): TypeSpec {
+        val tableNamesList = tableNames.joinToString(",") { "\"$it\"" }
+        return TypeSpec.anonymousClassBuilder(tableNamesList).apply {
+            superclass(INVALIDATION_OBSERVER)
+            addMethod(MethodSpec.methodBuilder("onInvalidated").apply {
+                returns(TypeName.VOID)
+                addAnnotation(Override::class.java)
+                addParameter(ParameterSpec.builder(
+                        ParameterizedTypeName.get(Set::class.java, String::class.java), "tables")
+                        .addAnnotation(NonNull::class.java)
+                        .build())
+                addModifiers(Modifier.PUBLIC)
+                addStatement("invalidate()")
+            }.build())
+        }.build()
+    }
+}
diff --git a/room/compiler/src/main/kotlin/android/arch/persistence/room/solver/query/result/PojoRowAdapter.kt b/room/compiler/src/main/kotlin/android/arch/persistence/room/solver/query/result/PojoRowAdapter.kt
new file mode 100644
index 0000000..b2cc1e0
--- /dev/null
+++ b/room/compiler/src/main/kotlin/android/arch/persistence/room/solver/query/result/PojoRowAdapter.kt
@@ -0,0 +1,145 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.arch.persistence.room.solver.query.result
+
+import android.arch.persistence.room.ext.L
+import android.arch.persistence.room.ext.S
+import android.arch.persistence.room.ext.T
+import android.arch.persistence.room.ext.typeName
+import android.arch.persistence.room.processor.Context
+import android.arch.persistence.room.processor.ProcessorErrors
+import android.arch.persistence.room.solver.CodeGenScope
+import android.arch.persistence.room.verifier.QueryResultInfo
+import android.arch.persistence.room.vo.Field
+import android.arch.persistence.room.vo.FieldWithIndex
+import android.arch.persistence.room.vo.Pojo
+import android.arch.persistence.room.vo.RelationCollector
+import android.arch.persistence.room.vo.Warning
+import android.arch.persistence.room.writer.FieldReadWriteWriter
+import com.squareup.javapoet.TypeName
+import stripNonJava
+import javax.lang.model.type.TypeMirror
+
+/**
+ * Creates the entity from the given info.
+ * <p>
+ * The info comes from the query processor so we know about the order of columns in the result etc.
+ */
+class PojoRowAdapter(context: Context, val info: QueryResultInfo,
+                     val pojo: Pojo, out: TypeMirror) : RowAdapter(out) {
+    val mapping: Mapping
+    val relationCollectors: List<RelationCollector>
+
+    init {
+        // toMutableList documentation is not clear if it copies so lets be safe.
+        val remainingFields = pojo.fields.mapTo(mutableListOf<Field>(), { it })
+        val unusedColumns = arrayListOf<String>()
+        val matchedFields = info.columns.map { column ->
+            // first check remaining, otherwise check any. maybe developer wants to map the same
+            // column into 2 fields. (if they want to post process etc)
+            val field = remainingFields.firstOrNull { it.columnName == column.name } ?:
+                    pojo.fields.firstOrNull { it.columnName == column.name }
+            if (field == null) {
+                unusedColumns.add(column.name)
+                null
+            } else {
+                remainingFields.remove(field)
+                field
+            }
+        }.filterNotNull()
+        if (unusedColumns.isNotEmpty() || remainingFields.isNotEmpty()) {
+            val warningMsg = ProcessorErrors.cursorPojoMismatch(
+                    pojoTypeName = pojo.typeName,
+                    unusedColumns = unusedColumns,
+                    allColumns = info.columns.map { it.name },
+                    unusedFields = remainingFields,
+                    allFields = pojo.fields
+            )
+            context.logger.w(Warning.CURSOR_MISMATCH, null, warningMsg)
+        }
+        if (matchedFields.isEmpty()) {
+            context.logger.e(ProcessorErrors.CANNOT_FIND_QUERY_RESULT_ADAPTER)
+        }
+
+        relationCollectors = RelationCollector.createCollectors(context, pojo.relations)
+
+        mapping = Mapping(
+                matchedFields = matchedFields,
+                unusedColumns = unusedColumns,
+                unusedFields = remainingFields
+        )
+    }
+
+    fun relationTableNames(): List<String> {
+        return relationCollectors.flatMap {
+            val queryTableNames = it.loadAllQuery.tables.map { it.name }
+            if (it.rowAdapter is PojoRowAdapter) {
+                it.rowAdapter.relationTableNames() + queryTableNames
+            } else {
+                queryTableNames
+            }
+        }.distinct()
+    }
+
+    override fun onCursorReady(cursorVarName: String, scope: CodeGenScope) {
+        relationCollectors.forEach { it.writeInitCode(scope) }
+        mapping.fieldsWithIndices = mapping.matchedFields.map {
+            val indexVar = scope.getTmpVar("_cursorIndexOf${it.name.stripNonJava().capitalize()}")
+            scope.builder().addStatement("final $T $L = $L.getColumnIndexOrThrow($S)",
+                    TypeName.INT, indexVar, cursorVarName, it.columnName)
+            FieldWithIndex(field = it, indexVar = indexVar, alwaysExists = true)
+        }
+    }
+
+    override fun convert(outVarName: String, cursorVarName: String, scope: CodeGenScope) {
+        scope.builder().apply {
+            FieldReadWriteWriter.readFromCursor(
+                    outVar = outVarName,
+                    outPojo = pojo,
+                    cursorVar = cursorVarName,
+                    fieldsWithIndices = mapping.fieldsWithIndices,
+                    scope = scope)
+            relationCollectors.forEach {
+                it.writeReadParentKeyCode(
+                        cursorVarName = cursorVarName,
+                        itemVar = outVarName,
+                        fieldsWithIndices = mapping.fieldsWithIndices,
+                        scope = scope)
+            }
+        }
+    }
+
+    override fun onCursorFinished(): ((CodeGenScope) -> Unit)? =
+            if (relationCollectors.isEmpty()) {
+                // it is important to return empty to notify that we don't need any post process
+                // task
+                null
+            } else {
+                { scope ->
+                    relationCollectors.forEach { collector ->
+                        collector.writeCollectionCode(scope)
+                    }
+                }
+            }
+
+    data class Mapping(val matchedFields: List<Field>,
+                       val unusedColumns: List<String>,
+                       val unusedFields: List<Field>) {
+        // set when cursor is ready.
+        lateinit var fieldsWithIndices: List<FieldWithIndex>
+    }
+}
diff --git a/room/compiler/src/main/kotlin/android/arch/persistence/room/solver/query/result/QueryResultAdapter.kt b/room/compiler/src/main/kotlin/android/arch/persistence/room/solver/query/result/QueryResultAdapter.kt
new file mode 100644
index 0000000..aaf17cd
--- /dev/null
+++ b/room/compiler/src/main/kotlin/android/arch/persistence/room/solver/query/result/QueryResultAdapter.kt
@@ -0,0 +1,30 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.arch.persistence.room.solver.query.result
+
+import android.arch.persistence.room.solver.CodeGenScope
+
+/**
+ * Gets a Cursor and converts it into the return type of a method annotated with @Query.
+ */
+abstract class QueryResultAdapter(val rowAdapter : RowAdapter?) {
+    abstract fun convert(outVarName: String, cursorVarName: String, scope: CodeGenScope)
+    fun accessedTableNames(): List<String> {
+        return (rowAdapter as? PojoRowAdapter)?.relationTableNames() ?: emptyList<String>()
+    }
+
+}
diff --git a/room/compiler/src/main/kotlin/android/arch/persistence/room/solver/query/result/QueryResultBinder.kt b/room/compiler/src/main/kotlin/android/arch/persistence/room/solver/query/result/QueryResultBinder.kt
new file mode 100644
index 0000000..205bd88
--- /dev/null
+++ b/room/compiler/src/main/kotlin/android/arch/persistence/room/solver/query/result/QueryResultBinder.kt
@@ -0,0 +1,36 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.arch.persistence.room.solver.query.result
+
+import android.arch.persistence.room.solver.CodeGenScope
+import com.squareup.javapoet.FieldSpec
+
+/**
+ * Connects the query, db and the ResultAdapter.
+ * <p>
+ * The default implementation is InstantResultBinder. If the query is deferred rather than executed
+ * directly, such alternative implementations can be implement using this interface (e.g. LiveData,
+ * Rx, caching etc)
+ */
+abstract class QueryResultBinder(val adapter: QueryResultAdapter?) {
+    /**
+     * receives the sql, bind args and adapter and generates the code that runs the query
+     * and returns the result.
+     */
+    abstract fun convertAndReturn(roomSQLiteQueryVar: String,
+                                  dbField: FieldSpec, scope: CodeGenScope)
+}
diff --git a/room/compiler/src/main/kotlin/android/arch/persistence/room/solver/query/result/RowAdapter.kt b/room/compiler/src/main/kotlin/android/arch/persistence/room/solver/query/result/RowAdapter.kt
new file mode 100644
index 0000000..581baf2
--- /dev/null
+++ b/room/compiler/src/main/kotlin/android/arch/persistence/room/solver/query/result/RowAdapter.kt
@@ -0,0 +1,43 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.arch.persistence.room.solver.query.result
+
+import android.arch.persistence.room.solver.CodeGenScope
+import javax.lang.model.type.TypeMirror
+
+/**
+ * Converts a row of a cursor result into an Entity or a primitive.
+ * <p>
+ * An instance of this is created for each usage so that it can keep local variables.
+ */
+abstract class RowAdapter(val out : TypeMirror) {
+    /**
+     * Called when cursor variable is ready, good place to put initialization code.
+     */
+    open fun onCursorReady(cursorVarName: String, scope : CodeGenScope) {}
+
+    /**
+     * Called to convert a single row.
+     */
+    abstract fun convert(outVarName: String, cursorVarName: String, scope: CodeGenScope)
+
+    /**
+     * Called when the cursor is finished. It is important to return null if no operation is
+     * necessary so that caller can understand that we can do lazy loading.
+     */
+    open fun onCursorFinished() : ((scope : CodeGenScope) -> Unit)? = null
+}
diff --git a/room/compiler/src/main/kotlin/android/arch/persistence/room/solver/query/result/SingleColumnRowAdapter.kt b/room/compiler/src/main/kotlin/android/arch/persistence/room/solver/query/result/SingleColumnRowAdapter.kt
new file mode 100644
index 0000000..8237177
--- /dev/null
+++ b/room/compiler/src/main/kotlin/android/arch/persistence/room/solver/query/result/SingleColumnRowAdapter.kt
@@ -0,0 +1,29 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.arch.persistence.room.solver.query.result
+
+import android.arch.persistence.room.solver.CodeGenScope
+import android.arch.persistence.room.solver.types.CursorValueReader
+
+/**
+ * Wraps a row adapter when there is only 1 item  with 1 column in the response.
+ */
+class SingleColumnRowAdapter(val reader: CursorValueReader) : RowAdapter(reader.typeMirror()) {
+    override fun convert(outVarName: String, cursorVarName: String, scope: CodeGenScope) {
+        reader.readFromCursor(outVarName, cursorVarName, "0", scope)
+    }
+}
diff --git a/room/compiler/src/main/kotlin/android/arch/persistence/room/solver/query/result/SingleEntityQueryResultAdapter.kt b/room/compiler/src/main/kotlin/android/arch/persistence/room/solver/query/result/SingleEntityQueryResultAdapter.kt
new file mode 100644
index 0000000..3086894
--- /dev/null
+++ b/room/compiler/src/main/kotlin/android/arch/persistence/room/solver/query/result/SingleEntityQueryResultAdapter.kt
@@ -0,0 +1,43 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.arch.persistence.room.solver.query.result
+
+import android.arch.persistence.room.ext.L
+import android.arch.persistence.room.ext.T
+import android.arch.persistence.room.ext.typeName
+import android.arch.persistence.room.solver.CodeGenScope
+import defaultValue
+
+/**
+ * Wraps a row adapter when there is only 1 item in the result
+ */
+class SingleEntityQueryResultAdapter(rowAdapter: RowAdapter) : QueryResultAdapter(rowAdapter) {
+    val type = rowAdapter.out
+    override fun convert(outVarName: String, cursorVarName: String, scope: CodeGenScope) {
+        scope.builder().apply {
+            rowAdapter?.onCursorReady(cursorVarName, scope)
+            addStatement("final $T $L", type.typeName(), outVarName)
+            beginControlFlow("if($L.moveToFirst())", cursorVarName)
+                rowAdapter?.convert(outVarName, cursorVarName, scope)
+            nextControlFlow("else").apply {
+                addStatement("$L = $L", outVarName, rowAdapter?.out?.defaultValue())
+            }
+            endControlFlow()
+            rowAdapter?.onCursorFinished()?.invoke(scope)
+        }
+    }
+}
diff --git a/room/compiler/src/main/kotlin/android/arch/persistence/room/solver/types/BoxedBooleanToBoxedIntConverter.kt b/room/compiler/src/main/kotlin/android/arch/persistence/room/solver/types/BoxedBooleanToBoxedIntConverter.kt
new file mode 100644
index 0000000..552da9e
--- /dev/null
+++ b/room/compiler/src/main/kotlin/android/arch/persistence/room/solver/types/BoxedBooleanToBoxedIntConverter.kt
@@ -0,0 +1,50 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.arch.persistence.room.solver.types
+
+import android.arch.persistence.room.ext.L
+import android.arch.persistence.room.solver.CodeGenScope
+import javax.annotation.processing.ProcessingEnvironment
+
+/**
+ * int to boolean adapter.
+ */
+object BoxedBooleanToBoxedIntConverter {
+    fun create(processingEnvironment: ProcessingEnvironment): List<TypeConverter> {
+        val tBoolean = processingEnvironment.elementUtils.getTypeElement("java.lang.Boolean")
+                .asType()
+        val tInt = processingEnvironment.elementUtils.getTypeElement("java.lang.Integer")
+                .asType()
+        return listOf(
+                object : TypeConverter(tBoolean, tInt) {
+                    override fun convert(inputVarName: String, outputVarName: String,
+                                         scope: CodeGenScope) {
+                        scope.builder().addStatement("$L = $L == null ? null : ($L ? 1 : 0)",
+                                outputVarName, inputVarName, inputVarName)
+                    }
+                },
+                object : TypeConverter(tInt, tBoolean) {
+                    override fun convert(inputVarName: String, outputVarName: String,
+                                         scope: CodeGenScope) {
+                        scope.builder().addStatement("$L = $L == null ? null : $L != 0",
+                                outputVarName, inputVarName, inputVarName)
+                    }
+
+                }
+        )
+    }
+}
diff --git a/room/compiler/src/main/kotlin/android/arch/persistence/room/solver/types/BoxedPrimitiveColumnTypeAdapter.kt b/room/compiler/src/main/kotlin/android/arch/persistence/room/solver/types/BoxedPrimitiveColumnTypeAdapter.kt
new file mode 100644
index 0000000..1b34b46
--- /dev/null
+++ b/room/compiler/src/main/kotlin/android/arch/persistence/room/solver/types/BoxedPrimitiveColumnTypeAdapter.kt
@@ -0,0 +1,71 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.arch.persistence.room.solver.types
+
+import android.arch.persistence.room.ext.L
+import android.arch.persistence.room.solver.CodeGenScope
+import com.google.auto.common.MoreTypes
+import javax.annotation.processing.ProcessingEnvironment
+import javax.lang.model.type.TypeMirror
+
+/**
+ * Adapters for all boxed primitives that has direct cursor mappings.
+ */
+open class BoxedPrimitiveColumnTypeAdapter(boxed : TypeMirror,
+                                           val primitiveAdapter : PrimitiveColumnTypeAdapter)
+            : ColumnTypeAdapter(boxed, primitiveAdapter.typeAffinity) {
+    companion object {
+        fun createBoxedPrimitiveAdapters(processingEnvironment: ProcessingEnvironment,
+                                    primitiveAdapters : List<PrimitiveColumnTypeAdapter>)
+                : List<ColumnTypeAdapter> {
+
+            return primitiveAdapters.map {
+                BoxedPrimitiveColumnTypeAdapter(
+                        processingEnvironment.typeUtils
+                                .boxedClass(MoreTypes.asPrimitiveType(it.out)).asType(),
+                        it
+                )
+            }
+        }
+    }
+
+    override fun bindToStmt(stmtName: String, indexVarName: String, valueVarName: String,
+                            scope: CodeGenScope) {
+        scope.builder().apply {
+            beginControlFlow("if ($L == null)", valueVarName).apply {
+                addStatement("$L.bindNull($L)", stmtName, indexVarName)
+            }
+            nextControlFlow("else").apply {
+                primitiveAdapter.bindToStmt(stmtName, indexVarName, valueVarName, scope)
+            }
+            endControlFlow()
+        }
+    }
+
+    override fun readFromCursor(outVarName: String, cursorVarName: String, indexVarName: String,
+                                scope: CodeGenScope) {
+        scope.builder().apply {
+            beginControlFlow("if ($L.isNull($L))", cursorVarName, indexVarName).apply {
+                addStatement("$L = null", outVarName)
+            }
+            nextControlFlow("else").apply {
+                primitiveAdapter.readFromCursor(outVarName, cursorVarName, indexVarName, scope)
+            }
+            endControlFlow()
+        }
+    }
+}
diff --git a/room/compiler/src/main/kotlin/android/arch/persistence/room/solver/types/ByteArrayColumnTypeAdapter.kt b/room/compiler/src/main/kotlin/android/arch/persistence/room/solver/types/ByteArrayColumnTypeAdapter.kt
new file mode 100644
index 0000000..014ea44
--- /dev/null
+++ b/room/compiler/src/main/kotlin/android/arch/persistence/room/solver/types/ByteArrayColumnTypeAdapter.kt
@@ -0,0 +1,44 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.arch.persistence.room.solver.types
+
+import android.arch.persistence.room.ext.L
+import android.arch.persistence.room.parser.SQLTypeAffinity
+import android.arch.persistence.room.solver.CodeGenScope
+import javax.annotation.processing.ProcessingEnvironment
+import javax.lang.model.type.TypeKind
+
+class ByteArrayColumnTypeAdapter(env : ProcessingEnvironment) : ColumnTypeAdapter(
+        out = env.typeUtils.getArrayType(env.typeUtils.getPrimitiveType(TypeKind.BYTE)),
+        typeAffinity = SQLTypeAffinity.BLOB) {
+    override fun readFromCursor(outVarName: String, cursorVarName: String, indexVarName: String,
+                                scope: CodeGenScope) {
+        scope.builder()
+                .addStatement("$L = $L.getBlob($L)", outVarName, cursorVarName, indexVarName)
+    }
+
+    override fun bindToStmt(stmtName: String, indexVarName: String, valueVarName: String,
+                            scope: CodeGenScope) {
+        scope.builder().apply {
+            beginControlFlow("if ($L == null)", valueVarName)
+                    .addStatement("$L.bindNull($L)", stmtName, indexVarName)
+            nextControlFlow("else")
+                    .addStatement("$L.bindBlob($L, $L)", stmtName, indexVarName, valueVarName)
+            endControlFlow()
+        }
+    }
+}
diff --git a/room/compiler/src/main/kotlin/android/arch/persistence/room/solver/types/ColumnTypeAdapter.kt b/room/compiler/src/main/kotlin/android/arch/persistence/room/solver/types/ColumnTypeAdapter.kt
new file mode 100644
index 0000000..d82dc79
--- /dev/null
+++ b/room/compiler/src/main/kotlin/android/arch/persistence/room/solver/types/ColumnTypeAdapter.kt
@@ -0,0 +1,31 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.arch.persistence.room.solver.types
+
+import android.arch.persistence.room.parser.SQLTypeAffinity
+import com.squareup.javapoet.TypeName
+import javax.lang.model.type.TypeMirror
+
+/**
+ * A code generator that can read a field from Cursor and write a field to a Statement
+ */
+abstract class ColumnTypeAdapter(val out: TypeMirror, val typeAffinity: SQLTypeAffinity) :
+        StatementValueBinder, CursorValueReader {
+    val outTypeName: TypeName by lazy { TypeName.get(out) }
+    override fun typeMirror() = out
+    override fun affinity(): SQLTypeAffinity = typeAffinity
+}
diff --git a/room/compiler/src/main/kotlin/android/arch/persistence/room/solver/types/CompositeAdapter.kt b/room/compiler/src/main/kotlin/android/arch/persistence/room/solver/types/CompositeAdapter.kt
new file mode 100644
index 0000000..a8678ff
--- /dev/null
+++ b/room/compiler/src/main/kotlin/android/arch/persistence/room/solver/types/CompositeAdapter.kt
@@ -0,0 +1,57 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.arch.persistence.room.solver.types
+
+import android.arch.persistence.room.ext.L
+import android.arch.persistence.room.ext.T
+import android.arch.persistence.room.solver.CodeGenScope
+import javax.lang.model.type.TypeMirror
+
+/**
+ * A column adapter that uses a type converter to do the conversion. The type converter may be
+ * a composite one.
+ */
+class CompositeAdapter(out: TypeMirror, val columnTypeAdapter: ColumnTypeAdapter,
+                       val intoStatementConverter: TypeConverter?,
+                       val fromCursorConverter: TypeConverter?)
+    : ColumnTypeAdapter(out, columnTypeAdapter.typeAffinity) {
+    override fun readFromCursor(outVarName: String, cursorVarName: String, indexVarName: String,
+                                scope: CodeGenScope) {
+        if (fromCursorConverter == null) {
+            return
+        }
+        scope.builder().apply {
+            val tmpCursorValue = scope.getTmpVar()
+            addStatement("final $T $L", columnTypeAdapter.outTypeName, tmpCursorValue)
+            columnTypeAdapter.readFromCursor(tmpCursorValue, cursorVarName, indexVarName, scope)
+            fromCursorConverter.convert(tmpCursorValue, outVarName, scope)
+        }
+    }
+
+    override fun bindToStmt(stmtName: String, indexVarName: String, valueVarName: String,
+                            scope: CodeGenScope) {
+        if (intoStatementConverter == null) {
+            return
+        }
+        scope.builder().apply {
+            val tmpVar = scope.getTmpVar()
+            addStatement("final $T $L", columnTypeAdapter.out, tmpVar)
+            intoStatementConverter.convert(valueVarName, tmpVar, scope)
+            columnTypeAdapter.bindToStmt(stmtName, indexVarName, tmpVar, scope)
+        }
+    }
+}
diff --git a/room/compiler/src/main/kotlin/android/arch/persistence/room/solver/types/CompositeTypeConverter.kt b/room/compiler/src/main/kotlin/android/arch/persistence/room/solver/types/CompositeTypeConverter.kt
new file mode 100644
index 0000000..86a5272
--- /dev/null
+++ b/room/compiler/src/main/kotlin/android/arch/persistence/room/solver/types/CompositeTypeConverter.kt
@@ -0,0 +1,37 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.arch.persistence.room.solver.types
+
+import android.arch.persistence.room.ext.L
+import android.arch.persistence.room.ext.T
+import android.arch.persistence.room.ext.typeName
+import android.arch.persistence.room.solver.CodeGenScope
+
+/**
+ * combines 2 type converters
+ */
+class CompositeTypeConverter(val conv1 : TypeConverter, val conv2 : TypeConverter) : TypeConverter(
+        conv1.from, conv2.to) {
+    override fun convert(inputVarName: String, outputVarName: String, scope: CodeGenScope) {
+        scope.builder().apply {
+            val tmp = scope.getTmpVar()
+            addStatement("final $T $L", conv1.to.typeName(), tmp)
+            conv1.convert(inputVarName, tmp, scope)
+            conv2.convert(tmp, outputVarName, scope)
+        }
+    }
+}
diff --git a/room/compiler/src/main/kotlin/android/arch/persistence/room/solver/types/CursorValueReader.kt b/room/compiler/src/main/kotlin/android/arch/persistence/room/solver/types/CursorValueReader.kt
new file mode 100644
index 0000000..e6726cd
--- /dev/null
+++ b/room/compiler/src/main/kotlin/android/arch/persistence/room/solver/types/CursorValueReader.kt
@@ -0,0 +1,32 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.arch.persistence.room.solver.types
+
+import android.arch.persistence.room.parser.SQLTypeAffinity
+import android.arch.persistence.room.solver.CodeGenScope
+import javax.lang.model.type.TypeMirror
+
+/**
+ * Reads value from a cursor at the given index.
+ * see: StatementValueBinder
+ */
+interface CursorValueReader {
+    fun affinity() : SQLTypeAffinity
+    fun typeMirror() : TypeMirror
+    fun readFromCursor(outVarName : String, cursorVarName: String, indexVarName: String,
+                                scope: CodeGenScope)
+}
diff --git a/room/compiler/src/main/kotlin/android/arch/persistence/room/solver/types/CustomTypeConverterWrapper.kt b/room/compiler/src/main/kotlin/android/arch/persistence/room/solver/types/CustomTypeConverterWrapper.kt
new file mode 100644
index 0000000..b4c7dc2
--- /dev/null
+++ b/room/compiler/src/main/kotlin/android/arch/persistence/room/solver/types/CustomTypeConverterWrapper.kt
@@ -0,0 +1,64 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.arch.persistence.room.solver.types
+
+import android.arch.persistence.room.ext.L
+import android.arch.persistence.room.ext.N
+import android.arch.persistence.room.ext.T
+import android.arch.persistence.room.solver.CodeGenScope
+import android.arch.persistence.room.vo.CustomTypeConverter
+import android.arch.persistence.room.writer.ClassWriter
+import com.squareup.javapoet.ClassName
+import com.squareup.javapoet.FieldSpec
+import javax.lang.model.element.Modifier
+
+/**
+ * Wraps a type converter specified by the developer and forwards calls to it.
+ */
+class CustomTypeConverterWrapper(val custom: CustomTypeConverter)
+    : TypeConverter(custom.from, custom.to) {
+
+    override fun convert(inputVarName: String, outputVarName: String, scope: CodeGenScope) {
+        scope.builder().apply {
+            if (custom.isStatic) {
+                addStatement("$L = $T.$L($L)",
+                        outputVarName, custom.typeName,
+                        custom.methodName, inputVarName)
+            } else {
+                addStatement("$L = $N.$L($L)",
+                        outputVarName, typeConverter(scope),
+                        custom.methodName, inputVarName)
+            }
+        }
+    }
+
+    fun typeConverter(scope: CodeGenScope) : FieldSpec {
+        val baseName = (custom.typeName as ClassName).simpleName().decapitalize()
+        return scope.writer.getOrCreateField(object : ClassWriter.SharedFieldSpec(
+                baseName, custom.typeName) {
+            override fun getUniqueKey(): String {
+                return "converter_${custom.typeName}"
+            }
+
+            override fun prepare(writer: ClassWriter, builder: FieldSpec.Builder) {
+                builder.addModifiers(Modifier.PRIVATE)
+                builder.addModifiers(Modifier.FINAL)
+                builder.initializer("new $T()", custom.typeName)
+            }
+        })
+    }
+}
diff --git a/room/compiler/src/main/kotlin/android/arch/persistence/room/solver/types/NoOpConverter.kt b/room/compiler/src/main/kotlin/android/arch/persistence/room/solver/types/NoOpConverter.kt
new file mode 100644
index 0000000..e803d30
--- /dev/null
+++ b/room/compiler/src/main/kotlin/android/arch/persistence/room/solver/types/NoOpConverter.kt
@@ -0,0 +1,37 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.arch.persistence.room.solver.types
+
+import android.arch.persistence.room.ext.L
+import android.arch.persistence.room.solver.CodeGenScope
+import javax.lang.model.type.TypeMirror
+
+/**
+ * Yes, we need this when user input is the same as the desired output.
+ * <p>
+ * Each query parameter receives an adapter that converts it into a String (or String[]). This
+ * TypeAdapter basically serves as a wrapper for converting String parameter into the String[] of
+ * the query. Not having this would require us to special case handle String, String[], List<String>
+ * etc.
+ */
+class NoOpConverter(type : TypeMirror) : TypeConverter(
+        type, type) {
+    override fun convert(inputVarName: String, outputVarName: String, scope: CodeGenScope) {
+        scope.builder()
+                .addStatement("$L = $L", outputVarName, inputVarName)
+    }
+}
diff --git a/room/compiler/src/main/kotlin/android/arch/persistence/room/solver/types/PrimitiveBooleanToIntConverter.kt b/room/compiler/src/main/kotlin/android/arch/persistence/room/solver/types/PrimitiveBooleanToIntConverter.kt
new file mode 100644
index 0000000..c65df5c
--- /dev/null
+++ b/room/compiler/src/main/kotlin/android/arch/persistence/room/solver/types/PrimitiveBooleanToIntConverter.kt
@@ -0,0 +1,46 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.arch.persistence.room.solver.types
+
+import android.arch.persistence.room.ext.L
+import android.arch.persistence.room.solver.CodeGenScope
+import javax.annotation.processing.ProcessingEnvironment
+import javax.lang.model.type.TypeKind.BOOLEAN
+import javax.lang.model.type.TypeKind.INT
+
+/**
+ * int to boolean adapter.
+ */
+object PrimitiveBooleanToIntConverter {
+    fun create(processingEnvironment: ProcessingEnvironment): List<TypeConverter> {
+        val tBoolean = processingEnvironment.typeUtils.getPrimitiveType(BOOLEAN)
+        val tInt = processingEnvironment.typeUtils.getPrimitiveType(INT)
+        return listOf(
+                object : TypeConverter(tBoolean, tInt) {
+                    override fun convert(inputVarName: String, outputVarName: String,
+                                         scope: CodeGenScope) {
+                        scope.builder().addStatement("$L = $L ? 1 : 0", outputVarName, inputVarName)
+                    }
+                },
+                object : TypeConverter(tInt, tBoolean) {
+                    override fun convert(inputVarName: String, outputVarName: String,
+                                         scope: CodeGenScope) {
+                        scope.builder().addStatement("$L = $L != 0", outputVarName, inputVarName)
+                    }
+                })
+    }
+}
diff --git a/room/compiler/src/main/kotlin/android/arch/persistence/room/solver/types/PrimitiveColumnTypeAdapter.kt b/room/compiler/src/main/kotlin/android/arch/persistence/room/solver/types/PrimitiveColumnTypeAdapter.kt
new file mode 100644
index 0000000..c04e7bb
--- /dev/null
+++ b/room/compiler/src/main/kotlin/android/arch/persistence/room/solver/types/PrimitiveColumnTypeAdapter.kt
@@ -0,0 +1,85 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.arch.persistence.room.solver.types
+
+import android.arch.persistence.room.ext.L
+import android.arch.persistence.room.ext.typeName
+import android.arch.persistence.room.parser.SQLTypeAffinity
+import android.arch.persistence.room.parser.SQLTypeAffinity.REAL
+import android.arch.persistence.room.solver.CodeGenScope
+import javax.annotation.processing.ProcessingEnvironment
+import javax.lang.model.type.PrimitiveType
+import javax.lang.model.type.TypeKind.BYTE
+import javax.lang.model.type.TypeKind.CHAR
+import javax.lang.model.type.TypeKind.DOUBLE
+import javax.lang.model.type.TypeKind.FLOAT
+import javax.lang.model.type.TypeKind.INT
+import javax.lang.model.type.TypeKind.LONG
+import javax.lang.model.type.TypeKind.SHORT
+
+/**
+ * Adapters for all primitives that has direct cursor mappings.
+ */
+open class PrimitiveColumnTypeAdapter(out: PrimitiveType,
+                                      val cursorGetter: String,
+                                      val stmtSetter: String,
+                                      typeAffinity : SQLTypeAffinity)
+        : ColumnTypeAdapter(out, typeAffinity) {
+    val cast =  if (cursorGetter == "get${out.typeName().toString().capitalize()}")
+                    ""
+                else
+                    "(${out.typeName()}) "
+
+    companion object {
+        fun createPrimitiveAdapters(processingEnvironment: ProcessingEnvironment)
+                : List<PrimitiveColumnTypeAdapter> {
+            return listOf(
+                    Triple(INT, "getInt", "bindLong"),
+                    Triple(SHORT, "getShort", "bindLong"),
+                    Triple(BYTE, "getShort", "bindLong"),
+                    Triple(LONG, "getLong", "bindLong"),
+                    Triple(CHAR, "getInt", "bindLong"),
+                    Triple(FLOAT, "getFloat", "bindDouble"),
+                    Triple(DOUBLE, "getDouble", "bindDouble")
+            ).map {
+                PrimitiveColumnTypeAdapter(
+                        out = processingEnvironment.typeUtils.getPrimitiveType(it.first),
+                        cursorGetter = it.second,
+                        stmtSetter = it.third,
+                        typeAffinity = when(it.first) {
+                            INT, SHORT, BYTE, LONG, CHAR -> SQLTypeAffinity.INTEGER
+                            FLOAT, DOUBLE -> REAL
+                            else -> throw IllegalArgumentException("invalid type")
+                        }
+                )
+            }
+        }
+    }
+
+    override fun bindToStmt(stmtName: String, indexVarName: String, valueVarName: String,
+                            scope: CodeGenScope) {
+        scope.builder()
+                .addStatement("$L.$L($L, $L)", stmtName, stmtSetter, indexVarName, valueVarName)
+    }
+
+    override fun readFromCursor(outVarName: String, cursorVarName: String, indexVarName: String,
+                                scope: CodeGenScope) {
+        scope.builder()
+                .addStatement("$L = $L$L.$L($L)", outVarName, cast, cursorVarName,
+                        cursorGetter, indexVarName)
+    }
+}
diff --git a/room/compiler/src/main/kotlin/android/arch/persistence/room/solver/types/StatementValueBinder.kt b/room/compiler/src/main/kotlin/android/arch/persistence/room/solver/types/StatementValueBinder.kt
new file mode 100644
index 0000000..c898268
--- /dev/null
+++ b/room/compiler/src/main/kotlin/android/arch/persistence/room/solver/types/StatementValueBinder.kt
@@ -0,0 +1,30 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.arch.persistence.room.solver.types
+
+import android.arch.persistence.room.solver.CodeGenScope
+import javax.lang.model.type.TypeMirror
+
+/**
+ * Binds a value into a statement
+ * see: CursorValueReader
+ */
+interface StatementValueBinder {
+    fun typeMirror() : TypeMirror
+    fun bindToStmt(stmtName: String, indexVarName: String, valueVarName: String,
+                            scope: CodeGenScope)
+}
diff --git a/room/compiler/src/main/kotlin/android/arch/persistence/room/solver/types/StringColumnTypeAdapter.kt b/room/compiler/src/main/kotlin/android/arch/persistence/room/solver/types/StringColumnTypeAdapter.kt
new file mode 100644
index 0000000..34ca61f
--- /dev/null
+++ b/room/compiler/src/main/kotlin/android/arch/persistence/room/solver/types/StringColumnTypeAdapter.kt
@@ -0,0 +1,43 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.arch.persistence.room.solver.types
+
+import android.arch.persistence.room.ext.L
+import android.arch.persistence.room.parser.SQLTypeAffinity.TEXT
+import android.arch.persistence.room.solver.CodeGenScope
+import javax.annotation.processing.ProcessingEnvironment
+
+class StringColumnTypeAdapter(processingEnvironment: ProcessingEnvironment)
+    : ColumnTypeAdapter((processingEnvironment.elementUtils.getTypeElement(
+        String::class.java.canonicalName)).asType(), TEXT) {
+    override fun readFromCursor(outVarName: String, cursorVarName: String, indexVarName: String,
+                                scope: CodeGenScope) {
+        scope.builder()
+                .addStatement("$L = $L.getString($L)", outVarName, cursorVarName, indexVarName)
+    }
+
+    override fun bindToStmt(stmtName: String, indexVarName: String, valueVarName: String,
+                            scope: CodeGenScope) {
+        scope.builder().apply {
+            beginControlFlow("if ($L == null)", valueVarName)
+                    .addStatement("$L.bindNull($L)", stmtName, indexVarName)
+            nextControlFlow("else")
+                    .addStatement("$L.bindString($L, $L)", stmtName, indexVarName, valueVarName)
+            endControlFlow()
+        }
+    }
+}
diff --git a/room/compiler/src/main/kotlin/android/arch/persistence/room/solver/types/TypeConverter.kt b/room/compiler/src/main/kotlin/android/arch/persistence/room/solver/types/TypeConverter.kt
new file mode 100644
index 0000000..134daf4
--- /dev/null
+++ b/room/compiler/src/main/kotlin/android/arch/persistence/room/solver/types/TypeConverter.kt
@@ -0,0 +1,28 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.arch.persistence.room.solver.types
+
+import android.arch.persistence.room.solver.CodeGenScope
+import com.squareup.javapoet.TypeName
+import javax.lang.model.type.TypeMirror
+
+/**
+ * A code generator that can convert from 1 type to another
+ */
+abstract class TypeConverter(val from: TypeMirror, val to: TypeMirror) {
+    abstract fun convert(inputVarName: String, outputVarName: String, scope: CodeGenScope)
+}
diff --git a/room/compiler/src/main/kotlin/android/arch/persistence/room/verifier/ColumnInfo.kt b/room/compiler/src/main/kotlin/android/arch/persistence/room/verifier/ColumnInfo.kt
new file mode 100644
index 0000000..e249871
--- /dev/null
+++ b/room/compiler/src/main/kotlin/android/arch/persistence/room/verifier/ColumnInfo.kt
@@ -0,0 +1,24 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.arch.persistence.room.verifier
+
+import android.arch.persistence.room.parser.SQLTypeAffinity
+
+/**
+ * Represents a column in a query response
+ */
+data class ColumnInfo(val name : String, val type : SQLTypeAffinity)
diff --git a/room/compiler/src/main/kotlin/android/arch/persistence/room/verifier/DatabaseVerificaitonErrors.kt b/room/compiler/src/main/kotlin/android/arch/persistence/room/verifier/DatabaseVerificaitonErrors.kt
new file mode 100644
index 0000000..a744894
--- /dev/null
+++ b/room/compiler/src/main/kotlin/android/arch/persistence/room/verifier/DatabaseVerificaitonErrors.kt
@@ -0,0 +1,40 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.arch.persistence.room.verifier
+
+import java.sql.SQLException
+
+object DatabaseVerificaitonErrors {
+    private val CANNOT_CREATE_TABLE : String = "Create table statement had an error: %s"
+    fun cannotCreateTable(exception: SQLException) : String {
+        return CANNOT_CREATE_TABLE.format(exception.message)
+    }
+
+    private val CANNOT_VERIFY_QUERY : String = "There is a problem with the query: %s"
+    fun cannotVerifyQuery(exception: SQLException) : String {
+        return CANNOT_VERIFY_QUERY.format(exception.message)
+    }
+
+    private val CANNOT_CREATE_SQLITE_CONNECTION : String = "Room cannot create an SQLite" +
+            " connection to verify the queries. Query verification will be disabled. Error: %s"
+    fun cannotCreateConnection(exception: Exception) : String {
+        return CANNOT_CREATE_SQLITE_CONNECTION.format(exception.message)
+    }
+
+    val CANNOT_GET_TMP_JAVA_DIR = "Cannot read tmp java dir which is necessary to load sqlite" +
+            " lib. Database SQL verification will be disabled"
+}
diff --git a/room/compiler/src/main/kotlin/android/arch/persistence/room/verifier/DatabaseVerifier.kt b/room/compiler/src/main/kotlin/android/arch/persistence/room/verifier/DatabaseVerifier.kt
new file mode 100644
index 0000000..8475f3a
--- /dev/null
+++ b/room/compiler/src/main/kotlin/android/arch/persistence/room/verifier/DatabaseVerifier.kt
@@ -0,0 +1,89 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.arch.persistence.room.verifier
+
+import columnInfo
+import android.arch.persistence.room.processor.Context
+import android.arch.persistence.room.vo.Entity
+import android.arch.persistence.room.vo.Warning
+import java.io.File
+import java.sql.Connection
+import java.sql.DriverManager
+import java.sql.SQLException
+import java.util.UUID
+import javax.lang.model.element.Element
+
+/**
+ * Builds an in-memory version of the database and verifies the queries against it.
+ * This class is also used to resolve the return types.
+ */
+class DatabaseVerifier private constructor(
+        val connection : Connection, val context : Context, val entities : List<Entity>) {
+    companion object {
+        /**
+         * Tries to create a verifier but returns null if it cannot find the driver.
+         */
+        fun create(context: Context, element: Element, entities: List<Entity>) : DatabaseVerifier? {
+            return try {
+                // see: https://github.com/xerial/sqlite-jdbc/issues/97
+                val tmpDir = System.getProperty("java.io.tmpdir")
+                if (tmpDir == null) {
+                    context.logger.w(Warning.MISSING_JAVA_TMP_DIR,
+                            element, DatabaseVerificaitonErrors.CANNOT_GET_TMP_JAVA_DIR)
+                    return null
+                }
+                val outDir = File(tmpDir, "room-${UUID.randomUUID()}")
+                outDir.mkdirs()
+                outDir.deleteOnExit()
+                System.setProperty("org.sqlite.tmpdir", outDir.absolutePath)
+                //force load:
+                Class.forName("org.sqlite.JDBC")
+                val connection = DriverManager.getConnection("jdbc:sqlite::memory:")
+                DatabaseVerifier(connection, context, entities)
+            } catch (ex : Exception) {
+                context.logger.w(Warning.CANNOT_CREATE_VERIFICATION_DATABASE, element,
+                        DatabaseVerificaitonErrors.cannotCreateConnection(ex))
+                null
+            }
+        }
+    }
+    init {
+        entities.forEach { entity ->
+            val stmt = connection.createStatement()
+            stmt.executeUpdate(entity.createTableQuery)
+        }
+    }
+
+    fun analyze(sql : String) : QueryResultInfo {
+        return try {
+            val stmt = connection.prepareStatement(sql)
+            QueryResultInfo(stmt.columnInfo())
+        } catch (ex : SQLException) {
+            QueryResultInfo(emptyList(), ex)
+        }
+    }
+
+    fun closeConnection() {
+        if (!connection.isClosed) {
+            try {
+                connection.close()
+            } catch (t : Throwable) {
+                //ignore.
+            }
+        }
+    }
+}
diff --git a/room/compiler/src/main/kotlin/android/arch/persistence/room/verifier/QueryResultInfo.kt b/room/compiler/src/main/kotlin/android/arch/persistence/room/verifier/QueryResultInfo.kt
new file mode 100644
index 0000000..4a99243
--- /dev/null
+++ b/room/compiler/src/main/kotlin/android/arch/persistence/room/verifier/QueryResultInfo.kt
@@ -0,0 +1,27 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.arch.persistence.room.verifier
+
+import java.sql.SQLException
+
+/**
+ * Represents the result of a query.
+ * <p>
+ * This information is obtained by preparing the query against an in memory database at compile
+ * time.
+ */
+data class QueryResultInfo(val columns : List<ColumnInfo>, val error : SQLException? = null)
diff --git a/room/compiler/src/main/kotlin/android/arch/persistence/room/verifier/jdbc_ext.kt b/room/compiler/src/main/kotlin/android/arch/persistence/room/verifier/jdbc_ext.kt
new file mode 100644
index 0000000..5ae9d6a
--- /dev/null
+++ b/room/compiler/src/main/kotlin/android/arch/persistence/room/verifier/jdbc_ext.kt
@@ -0,0 +1,62 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT 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 android.arch.persistence.room.parser.SQLTypeAffinity
+import android.arch.persistence.room.verifier.ColumnInfo
+import java.sql.PreparedStatement
+import java.sql.ResultSet
+import java.sql.ResultSetMetaData
+import java.sql.SQLException
+
+internal fun <T> ResultSet.collect(f: (ResultSet) -> T): List<T> {
+    val result = arrayListOf<T>()
+    try {
+        while (next()) {
+            result.add(f.invoke(this))
+        }
+    } finally {
+        close()
+    }
+    return result
+}
+
+private fun <T> PreparedStatement.map(f : (Int, ResultSetMetaData) ->  T) : List<T> {
+    val columnCount = try {
+        metaData.columnCount
+    } catch (ex : SQLException) {
+        // ignore, no-result query
+        0
+    }
+    // return is separate than data creation because we want to know who throws the exception
+    return (1.rangeTo(columnCount)).map { f(it, metaData) }
+}
+
+internal fun PreparedStatement.columnNames(): List<String> {
+    return map { index, data -> data.getColumnName(index) }
+}
+
+private fun PreparedStatement.tryGetAffinity(columnIndex : Int) : SQLTypeAffinity {
+    return try {
+        SQLTypeAffinity.valueOf(metaData.getColumnTypeName(columnIndex).capitalize())
+    } catch (ex : IllegalArgumentException) {
+        SQLTypeAffinity.NULL
+    }
+}
+
+internal fun PreparedStatement.columnInfo(): List<ColumnInfo> {
+    //see: http://sqlite.1065341.n5.nabble.com/Column-order-in-resultset-td23127.html
+    return map { index, data -> ColumnInfo(data.getColumnName(index), tryGetAffinity(index)) }
+}
diff --git a/room/compiler/src/main/kotlin/android/arch/persistence/room/vo/CallType.kt b/room/compiler/src/main/kotlin/android/arch/persistence/room/vo/CallType.kt
new file mode 100644
index 0000000..40cbcc4
--- /dev/null
+++ b/room/compiler/src/main/kotlin/android/arch/persistence/room/vo/CallType.kt
@@ -0,0 +1,23 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.arch.persistence.room.vo
+
+enum class CallType {
+    FIELD,
+    METHOD,
+    CONSTRUCTOR
+}
diff --git a/room/compiler/src/main/kotlin/android/arch/persistence/room/vo/Constructor.kt b/room/compiler/src/main/kotlin/android/arch/persistence/room/vo/Constructor.kt
new file mode 100644
index 0000000..fdc2a11
--- /dev/null
+++ b/room/compiler/src/main/kotlin/android/arch/persistence/room/vo/Constructor.kt
@@ -0,0 +1,55 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.arch.persistence.room.vo
+
+import javax.lang.model.element.ExecutableElement
+
+/**
+ * For each Entity / Pojo we process has a constructor. It might be the empty constructor or a
+ * constructor with fields.
+ */
+data class Constructor(val element : ExecutableElement, val params : List<Param>) {
+
+    fun hasField(field : Field) : Boolean {
+        return params.any {
+            when (it) {
+                is FieldParam -> it.field === field
+                is EmbeddedParam -> it.embedded.field === field
+                else -> false
+            }
+        }
+    }
+
+    class FieldParam(val field : Field) : Param(ParamType.FIELD) {
+        override fun log(): String = field.getPath()
+
+    }
+
+    class EmbeddedParam(val embedded: EmbeddedField) : Param(ParamType.EMBEDDED) {
+        override fun log(): String = embedded.field.getPath()
+
+    }
+
+    abstract class Param(val type : ParamType) {
+        abstract fun log() : String;
+    }
+
+    enum class ParamType {
+        FIELD,
+        EMBEDDED
+    }
+}
diff --git a/room/compiler/src/main/kotlin/android/arch/persistence/room/vo/CustomTypeConverter.kt b/room/compiler/src/main/kotlin/android/arch/persistence/room/vo/CustomTypeConverter.kt
new file mode 100644
index 0000000..5a1f0e6
--- /dev/null
+++ b/room/compiler/src/main/kotlin/android/arch/persistence/room/vo/CustomTypeConverter.kt
@@ -0,0 +1,37 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.arch.persistence.room.vo
+
+import android.arch.persistence.room.ext.hasAnyOf
+import android.arch.persistence.room.ext.typeName
+import com.squareup.javapoet.TypeName
+import javax.lang.model.element.ExecutableElement
+import javax.lang.model.element.Modifier
+import javax.lang.model.type.TypeMirror
+
+/**
+ * Generated when we parse a method annotated with TypeConverter.
+ */
+data class CustomTypeConverter(val type: TypeMirror,
+                               val method : ExecutableElement,
+                               val from: TypeMirror, val to: TypeMirror) {
+    val typeName: TypeName by lazy { type.typeName() }
+    val fromTypeName: TypeName by lazy { from.typeName() }
+    val toTypeName: TypeName by lazy { to.typeName() }
+    val methodName by lazy { method.simpleName.toString() }
+    val isStatic by lazy { method.hasAnyOf(Modifier.STATIC) }
+}
diff --git a/room/compiler/src/main/kotlin/android/arch/persistence/room/vo/Dao.kt b/room/compiler/src/main/kotlin/android/arch/persistence/room/vo/Dao.kt
new file mode 100644
index 0000000..227fa49
--- /dev/null
+++ b/room/compiler/src/main/kotlin/android/arch/persistence/room/vo/Dao.kt
@@ -0,0 +1,55 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.arch.persistence.room.vo
+
+import com.squareup.javapoet.ClassName
+import com.squareup.javapoet.TypeName
+import javax.lang.model.element.TypeElement
+import javax.lang.model.type.DeclaredType
+
+data class Dao(val element : TypeElement, val type : DeclaredType,
+               val queryMethods: List<QueryMethod>,
+               val insertionMethods : List<InsertionMethod>,
+               val deletionMethods : List<DeletionMethod>,
+               val updateMethods : List<UpdateMethod>,
+               val constructorParamType : TypeName?) {
+    // parsed dao might have a suffix if it is used in multiple databases.
+    private var suffix : String? = null
+    fun setSuffix(newSuffix : String) {
+        if (this.suffix != null) {
+            throw IllegalStateException("cannot set suffix twice")
+        }
+        this.suffix = if (newSuffix == "") "" else "_$newSuffix"
+    }
+
+    val typeName : ClassName by lazy { ClassName.get(element) }
+
+    val shortcutMethods : List<ShortcutMethod> by lazy {
+        deletionMethods + updateMethods
+    }
+
+    private val implClassName by lazy {
+        if (suffix == null) {
+            suffix = ""
+        }
+        "${typeName.simpleName()}${suffix}_Impl"
+    }
+
+    val implTypeName by lazy {
+        ClassName.get(typeName.packageName(), implClassName)
+    }
+}
diff --git a/room/compiler/src/main/kotlin/android/arch/persistence/room/vo/DaoMethod.kt b/room/compiler/src/main/kotlin/android/arch/persistence/room/vo/DaoMethod.kt
new file mode 100644
index 0000000..bd5e6b8
--- /dev/null
+++ b/room/compiler/src/main/kotlin/android/arch/persistence/room/vo/DaoMethod.kt
@@ -0,0 +1,24 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.arch.persistence.room.vo
+
+import javax.lang.model.element.Element
+
+/**
+ * References a method that returns a dao in a Database
+ */
+data class DaoMethod(val element : Element, val name : String, val dao : Dao)
diff --git a/room/compiler/src/main/kotlin/android/arch/persistence/room/vo/Database.kt b/room/compiler/src/main/kotlin/android/arch/persistence/room/vo/Database.kt
new file mode 100644
index 0000000..b118a32
--- /dev/null
+++ b/room/compiler/src/main/kotlin/android/arch/persistence/room/vo/Database.kt
@@ -0,0 +1,76 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.arch.persistence.room.vo
+
+import android.arch.persistence.room.RoomMasterTable
+import android.arch.persistence.room.migration.bundle.DatabaseBundle
+import android.arch.persistence.room.migration.bundle.SchemaBundle
+import com.squareup.javapoet.ClassName
+import org.apache.commons.codec.digest.DigestUtils
+import java.io.File
+import javax.lang.model.element.TypeElement
+import javax.lang.model.type.TypeMirror
+
+/**
+ * Holds information about a class annotated with Database.
+ */
+data class Database(val element: TypeElement,
+                    val type: TypeMirror,
+                    val entities: List<Entity>,
+                    val daoMethods: List<DaoMethod>,
+                    val version: Int,
+                    val exportSchema: Boolean,
+                    val enableForeignKeys : Boolean) {
+    val typeName: ClassName by lazy { ClassName.get(element) }
+
+    private val implClassName by lazy {
+        "${typeName.simpleNames().joinToString("_")}_Impl"
+    }
+
+    val implTypeName: ClassName by lazy {
+        ClassName.get(typeName.packageName(), implClassName)
+    }
+
+    val bundle by lazy {
+        DatabaseBundle(version, identityHash, entities.map(Entity::toBundle),
+                listOf(RoomMasterTable.CREATE_QUERY,
+                        RoomMasterTable.createInsertQuery(identityHash)))
+    }
+
+    /**
+     * Create a has that identifies this database definition so that at runtime we can check to
+     * ensure developer didn't forget to update the version.
+     */
+    val identityHash: String by lazy {
+        val entityDescriptions = entities
+                .sortedBy { it.tableName }
+                .map { it.createTableQuery }
+        val indexDescriptions = entities
+                .flatMap { entity ->
+                    entity.indices.map { index ->
+                        index.createQuery(entity.tableName)
+                    }
+                }
+        val input = (entityDescriptions + indexDescriptions).joinToString("¯\\_(ツ)_/¯")
+        DigestUtils.md5Hex(input)
+    }
+
+    fun exportSchema(file: File) {
+        val schemaBundle = SchemaBundle(SchemaBundle.LATEST_FORMAT, bundle)
+        SchemaBundle.serialize(schemaBundle, file)
+    }
+}
diff --git a/room/compiler/src/main/kotlin/android/arch/persistence/room/vo/DeletionMethod.kt b/room/compiler/src/main/kotlin/android/arch/persistence/room/vo/DeletionMethod.kt
new file mode 100644
index 0000000..c4adc54
--- /dev/null
+++ b/room/compiler/src/main/kotlin/android/arch/persistence/room/vo/DeletionMethod.kt
@@ -0,0 +1,24 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package android.arch.persistence.room.vo
+
+import javax.lang.model.element.ExecutableElement
+
+class DeletionMethod(element: ExecutableElement, name: String,
+                          entities: Map<String, Entity>, returnCount : Boolean,
+                          parameters: List<ShortcutQueryParameter>) : ShortcutMethod(
+        element, name, entities, returnCount, parameters
+)
diff --git a/room/compiler/src/main/kotlin/android/arch/persistence/room/vo/EmbeddedField.kt b/room/compiler/src/main/kotlin/android/arch/persistence/room/vo/EmbeddedField.kt
new file mode 100644
index 0000000..c6af79a
--- /dev/null
+++ b/room/compiler/src/main/kotlin/android/arch/persistence/room/vo/EmbeddedField.kt
@@ -0,0 +1,45 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.arch.persistence.room.vo
+
+import android.support.annotation.NonNull
+import android.arch.persistence.room.ext.hasAnnotation
+
+/**
+ * Used when a field is embedded inside an Entity or Pojo.
+ */
+// used in cache matching, must stay as a data class or implement equals
+data class EmbeddedField(val field : Field, val prefix : String = "",
+                         val parent : EmbeddedField?) {
+    val getter by lazy { field.getter }
+    val setter by lazy { field.setter }
+    val nonNull = field.element.hasAnnotation(NonNull::class)
+    lateinit var pojo: Pojo
+    val mRootParent: EmbeddedField by lazy {
+        parent?.mRootParent ?: this
+    }
+
+    fun isDescendantOf(other : EmbeddedField) : Boolean {
+        if (parent == other) {
+            return true
+        } else if (parent == null) {
+            return false
+        } else {
+            return parent.isDescendantOf(other)
+        }
+    }
+}
diff --git a/room/compiler/src/main/kotlin/android/arch/persistence/room/vo/Entity.kt b/room/compiler/src/main/kotlin/android/arch/persistence/room/vo/Entity.kt
new file mode 100644
index 0000000..06e7484
--- /dev/null
+++ b/room/compiler/src/main/kotlin/android/arch/persistence/room/vo/Entity.kt
@@ -0,0 +1,79 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.arch.persistence.room.vo
+
+import android.arch.persistence.room.migration.bundle.BundleUtil
+import android.arch.persistence.room.migration.bundle.EntityBundle
+import javax.lang.model.element.TypeElement
+import javax.lang.model.type.DeclaredType
+
+// TODO make data class when move to kotlin 1.1
+class Entity(element: TypeElement, val tableName: String, type: DeclaredType,
+             fields: List<Field>, embeddedFields: List<EmbeddedField>,
+             val primaryKey: PrimaryKey, val indices: List<Index>,
+             val foreignKeys: List<ForeignKey>,
+             constructor: Constructor?)
+    : Pojo(element, type, fields, embeddedFields, emptyList(), constructor) {
+
+    val createTableQuery by lazy {
+        createTableQuery(tableName)
+    }
+
+    fun createTableQuery(tableName : String) : String {
+        val definitions = (fields.map {
+            val autoIncrement = primaryKey.autoGenerateId && primaryKey.fields.contains(it)
+            it.databaseDefinition(autoIncrement)
+        } + createPrimaryKeyDefinition() + createForeignKeyDefinitions()).filterNotNull()
+        return "CREATE TABLE IF NOT EXISTS `$tableName` (${definitions.joinToString(", ")})"
+    }
+
+    private fun createForeignKeyDefinitions() : List<String> {
+        return foreignKeys.map { it.databaseDefinition() }
+    }
+
+    private fun createPrimaryKeyDefinition(): String? {
+        return if (primaryKey.fields.isEmpty() || primaryKey.autoGenerateId) {
+            null
+        } else {
+            val keys = primaryKey.fields
+                    .map { "`${it.columnName}`" }
+                    .joinToString(", ")
+            "PRIMARY KEY($keys)"
+        }
+    }
+
+    fun toBundle(): EntityBundle = EntityBundle(
+            tableName,
+            createTableQuery(BundleUtil.TABLE_NAME_PLACEHOLDER),
+            fields.map {it.toBundle()},
+            primaryKey.toBundle(),
+            indices.map { it.toBundle() },
+            foreignKeys.map { it.toBundle() })
+
+    fun isUnique(columns: List<String>) : Boolean {
+        return if (primaryKey.columnNames.size == columns.size
+                && primaryKey.columnNames.containsAll(columns)) {
+            true
+        } else {
+            indices.any { index ->
+                index.unique
+                        && index.fields.size == columns.size
+                        && index.columnNames.containsAll(columns)
+            }
+        }
+    }
+}
diff --git a/room/compiler/src/main/kotlin/android/arch/persistence/room/vo/Field.kt b/room/compiler/src/main/kotlin/android/arch/persistence/room/vo/Field.kt
new file mode 100644
index 0000000..1bfeaee
--- /dev/null
+++ b/room/compiler/src/main/kotlin/android/arch/persistence/room/vo/Field.kt
@@ -0,0 +1,120 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.arch.persistence.room.vo
+
+import android.arch.persistence.room.ext.typeName
+import android.arch.persistence.room.migration.bundle.FieldBundle
+import android.arch.persistence.room.parser.SQLTypeAffinity
+import android.arch.persistence.room.solver.types.CursorValueReader
+import android.arch.persistence.room.solver.types.StatementValueBinder
+import com.squareup.javapoet.TypeName
+import javax.lang.model.element.Element
+import javax.lang.model.type.TypeMirror
+// used in cache matching, must stay as a data class or implement equals
+data class Field(val element: Element, val name: String, val type: TypeMirror,
+                 var affinity: SQLTypeAffinity?,
+                 val columnName: String = name,
+                 /* means that this field does not belong to parent, instead, it belongs to a
+                 * embedded child of the main Pojo*/
+                 val parent: EmbeddedField? = null,
+                 // index might be removed when being merged into an Entity
+                 var indexed : Boolean = false) {
+    lateinit var getter: FieldGetter
+    lateinit var setter: FieldSetter
+    // binds the field into a statement
+    var statementBinder: StatementValueBinder? = null
+    // reads this field from a cursor column
+    var cursorValueReader: CursorValueReader? = null
+    val typeName: TypeName by lazy { type.typeName() }
+
+    /**
+     * Used when reporting errors on duplicate names
+     */
+    fun getPath() : String {
+        return if (parent == null) {
+            name
+        } else {
+            "${parent.field.getPath()} > $name"
+        }
+    }
+
+    private val pathWithDotNotation : String by lazy {
+        if (parent == null) {
+            name
+        } else {
+            "${parent.field.pathWithDotNotation}.$name"
+        }
+    }
+
+    /**
+     * List of names that include variations.
+     * e.g. if it is mUser, user is added to the list
+     * or if it is isAdmin, admin is added to the list
+     */
+    val nameWithVariations by lazy {
+        val result = arrayListOf(name)
+        if (name.length > 1) {
+            if (name.startsWith('_')) {
+                result.add(name.substring(1))
+            }
+            if (name.startsWith("m") && name[1].isUpperCase()) {
+                result.add(name.substring(1).decapitalize())
+            }
+
+            if (typeName == TypeName.BOOLEAN || typeName == TypeName.BOOLEAN.box()) {
+                if (name.length > 2 && name.startsWith("is") && name[2].isUpperCase()) {
+                    result.add(name.substring(2).decapitalize())
+                }
+                if (name.length > 3 && name.startsWith("has") && name[3].isUpperCase()) {
+                    result.add(name.substring(3).decapitalize())
+                }
+            }
+        }
+        result
+    }
+
+    val getterNameWithVariations by lazy {
+        nameWithVariations.map { "get${it.capitalize()}" } +
+                if (typeName == TypeName.BOOLEAN || typeName == TypeName.BOOLEAN.box()) {
+                    nameWithVariations.flatMap {
+                        listOf("is${it.capitalize()}", "has${it.capitalize()}")
+                    }
+                } else {
+                    emptyList()
+                }
+    }
+
+    val setterNameWithVariations by lazy {
+        nameWithVariations.map { "set${it.capitalize()}" }
+    }
+
+    /**
+     * definition to be used in create query
+     */
+    fun databaseDefinition(autoIncrementPKey : Boolean) : String {
+        val columnSpec = if (autoIncrementPKey) {
+            " PRIMARY KEY AUTOINCREMENT"
+        } else {
+            ""
+        }
+        return "`$columnName` ${(affinity ?: SQLTypeAffinity.TEXT).name}$columnSpec"
+    }
+
+    fun toBundle(): FieldBundle = FieldBundle(pathWithDotNotation, columnName,
+            affinity?.name ?: SQLTypeAffinity.TEXT.name
+    )
+}
diff --git a/room/compiler/src/main/kotlin/android/arch/persistence/room/vo/FieldGetter.kt b/room/compiler/src/main/kotlin/android/arch/persistence/room/vo/FieldGetter.kt
new file mode 100644
index 0000000..6713c2c
--- /dev/null
+++ b/room/compiler/src/main/kotlin/android/arch/persistence/room/vo/FieldGetter.kt
@@ -0,0 +1,36 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.arch.persistence.room.vo
+
+import android.arch.persistence.room.ext.L
+import android.arch.persistence.room.ext.T
+import com.squareup.javapoet.CodeBlock
+import com.squareup.javapoet.TypeName
+import javax.lang.model.type.TypeMirror
+
+data class FieldGetter(val name : String, val type : TypeMirror, val callType: CallType) {
+    fun writeGet(ownerVar: String, outVar: String, builder: CodeBlock.Builder) {
+        val stmt = when (callType) {
+            CallType.FIELD -> "final $T $L = $L.$L"
+            CallType.METHOD -> "final $T $L = $L.$L()"
+            CallType.CONSTRUCTOR -> null
+        }
+        stmt?.let {
+            builder.addStatement(stmt, TypeName.get(type), outVar, ownerVar, name)
+        }
+    }
+}
diff --git a/room/compiler/src/main/kotlin/android/arch/persistence/room/vo/FieldSetter.kt b/room/compiler/src/main/kotlin/android/arch/persistence/room/vo/FieldSetter.kt
new file mode 100644
index 0000000..5aa1806
--- /dev/null
+++ b/room/compiler/src/main/kotlin/android/arch/persistence/room/vo/FieldSetter.kt
@@ -0,0 +1,34 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.arch.persistence.room.vo
+
+import android.arch.persistence.room.ext.L
+import com.squareup.javapoet.CodeBlock
+import javax.lang.model.type.TypeMirror
+
+data class FieldSetter(val name: String, val type: TypeMirror, val callType: CallType) {
+    fun writeSet(ownerVar: String, inVar: String, builder: CodeBlock.Builder) {
+        val stmt = when (callType) {
+            CallType.FIELD -> "$L.$L = $L"
+            CallType.METHOD -> "$L.$L($L)"
+            CallType.CONSTRUCTOR -> null
+        }
+        stmt?.let {
+            builder.addStatement(stmt, ownerVar, name, inVar)
+        }
+    }
+}
diff --git a/room/compiler/src/main/kotlin/android/arch/persistence/room/vo/FieldWithIndex.kt b/room/compiler/src/main/kotlin/android/arch/persistence/room/vo/FieldWithIndex.kt
new file mode 100644
index 0000000..6ef6979
--- /dev/null
+++ b/room/compiler/src/main/kotlin/android/arch/persistence/room/vo/FieldWithIndex.kt
@@ -0,0 +1,36 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.arch.persistence.room.vo
+
+/**
+ * A common value object when we need to associate a Field with an Index
+ * variable.
+ * <p>
+ * If we are sure that the field will be there at compile time, we set it to always Exists so that
+ * the generated code does not check for -1 column indices.
+ */
+data class FieldWithIndex(val field : Field, val indexVar : String, val alwaysExists : Boolean) {
+    companion object {
+        fun byOrder(fields : List<Field>) : List<FieldWithIndex> {
+            return fields.mapIndexed { index, field ->
+                FieldWithIndex(field = field,
+                        indexVar = "${index + 1}",
+                        alwaysExists = true)
+            }
+        }
+    }
+}
diff --git a/room/compiler/src/main/kotlin/android/arch/persistence/room/vo/ForeignKey.kt b/room/compiler/src/main/kotlin/android/arch/persistence/room/vo/ForeignKey.kt
new file mode 100644
index 0000000..66cf3a0
--- /dev/null
+++ b/room/compiler/src/main/kotlin/android/arch/persistence/room/vo/ForeignKey.kt
@@ -0,0 +1,53 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.arch.persistence.room.vo
+
+import android.arch.persistence.room.migration.bundle.ForeignKeyBundle
+
+/**
+ * Keeps information about a foreign key.
+ */
+data class ForeignKey(val parentTable: String,
+                      val parentColumns: List<String>,
+                      val childFields: List<Field>,
+                      val onDelete: ForeignKeyAction,
+                      val onUpdate: ForeignKeyAction,
+                      val deferred: Boolean) {
+    fun databaseDefinition(): String {
+        return "FOREIGN KEY(${joinEscaped(childFields.map { it.columnName })})" +
+                " REFERENCES `$parentTable`(${joinEscaped(parentColumns)})" +
+                " ON UPDATE ${onUpdate.sqlName}" +
+                " ON DELETE ${onDelete.sqlName}" +
+                " ${deferredDeclaration()}"
+    }
+
+    private fun deferredDeclaration(): String {
+        return if (deferred) {
+            "DEFERRABLE INITIALLY DEFERRED"
+        } else {
+            ""
+        }
+    }
+
+    private fun joinEscaped(values: Iterable<String>) = values.joinToString(", ") { "`$it`" }
+
+    fun toBundle(): ForeignKeyBundle = ForeignKeyBundle(
+            parentTable, onDelete.sqlName, onUpdate.sqlName,
+            childFields.map { it.columnName },
+            parentColumns
+    )
+}
diff --git a/room/compiler/src/main/kotlin/android/arch/persistence/room/vo/ForeignKeyAction.kt b/room/compiler/src/main/kotlin/android/arch/persistence/room/vo/ForeignKeyAction.kt
new file mode 100644
index 0000000..1b85910
--- /dev/null
+++ b/room/compiler/src/main/kotlin/android/arch/persistence/room/vo/ForeignKeyAction.kt
@@ -0,0 +1,36 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.arch.persistence.room.vo
+
+import android.arch.persistence.room.ForeignKey
+
+/**
+ * Compiler representation of ForeignKey#Action.
+ */
+enum class ForeignKeyAction(val annotationValue : Int, val sqlName : String) {
+    NO_ACTION(ForeignKey.NO_ACTION, "NO ACTION"),
+    RESTRICT(ForeignKey.RESTRICT, "RESTRICT"),
+    SET_NULL(ForeignKey.SET_NULL, "SET NULL"),
+    SET_DEFAULT(ForeignKey.SET_DEFAULT, "SET DEFAULT"),
+    CASCADE(ForeignKey.CASCADE, "CASCADE");
+    companion object {
+        private val mapping by lazy {
+            ForeignKeyAction.values().associateBy { it.annotationValue }
+        }
+        fun fromAnnotationValue(value : Int?) = mapping[value]
+    }
+}
diff --git a/room/compiler/src/main/kotlin/android/arch/persistence/room/vo/Index.kt b/room/compiler/src/main/kotlin/android/arch/persistence/room/vo/Index.kt
new file mode 100644
index 0000000..60adf2b
--- /dev/null
+++ b/room/compiler/src/main/kotlin/android/arch/persistence/room/vo/Index.kt
@@ -0,0 +1,43 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.arch.persistence.room.vo
+
+import android.arch.persistence.room.migration.bundle.BundleUtil
+import android.arch.persistence.room.migration.bundle.IndexBundle
+
+/**
+ * Represents a processed index.
+ */
+data class Index(val name : String, val unique : Boolean, val fields : List<Field>) {
+
+    fun createQuery(tableName : String) : String {
+        val uniqueSQL = if (unique) {
+            "UNIQUE"
+        } else {
+            ""
+        }
+        return """
+            CREATE $uniqueSQL INDEX `$name`
+            ON `$tableName` (${fields.map { it.columnName }.joinToString(", ") { "`$it`"}})
+            """.trimIndent().replace(System.lineSeparator(), " ")
+    }
+
+    val columnNames by lazy { fields.map {it.columnName} }
+
+    fun toBundle(): IndexBundle = IndexBundle(name, unique, fields.map { it.columnName },
+            createQuery(BundleUtil.TABLE_NAME_PLACEHOLDER))
+}
diff --git a/room/compiler/src/main/kotlin/android/arch/persistence/room/vo/InsertionMethod.kt b/room/compiler/src/main/kotlin/android/arch/persistence/room/vo/InsertionMethod.kt
new file mode 100644
index 0000000..ad3688d
--- /dev/null
+++ b/room/compiler/src/main/kotlin/android/arch/persistence/room/vo/InsertionMethod.kt
@@ -0,0 +1,54 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.arch.persistence.room.vo
+
+import android.arch.persistence.room.OnConflictStrategy
+import android.arch.persistence.room.ext.typeName
+import com.squareup.javapoet.ArrayTypeName
+import com.squareup.javapoet.ParameterizedTypeName
+import com.squareup.javapoet.TypeName
+import javax.lang.model.element.ExecutableElement
+import javax.lang.model.type.TypeMirror
+
+data class InsertionMethod(val element: ExecutableElement, val name: String,
+                           @OnConflictStrategy val onConflict: Int,
+                           val entities: Map<String, Entity>, val returnType: TypeMirror,
+                           val insertionType: Type?,
+                           val parameters: List<ShortcutQueryParameter>) {
+    fun insertMethodTypeFor(param : ShortcutQueryParameter) : Type {
+        return if (insertionType == Type.INSERT_VOID || insertionType == null) {
+            Type.INSERT_VOID
+        } else if (!param.isMultiple) {
+            Type.INSERT_SINGLE_ID
+        } else {
+            insertionType
+        }
+    }
+
+    enum class Type(
+            // methodName matches EntityInsertionAdapter methods
+            val methodName : String, val returnTypeName : TypeName) {
+        INSERT_VOID("insert", TypeName.VOID), // return void
+        INSERT_SINGLE_ID("insertAndReturnId", TypeName.LONG), // return long
+        INSERT_ID_ARRAY("insertAndReturnIdsArray",
+                ArrayTypeName.of(TypeName.LONG)), // return long[]
+        INSERT_ID_ARRAY_BOX("insertAndReturnIdsArrayBox",
+                ArrayTypeName.of(TypeName.LONG.box())), // return Long[]
+        INSERT_ID_LIST("insertAndReturnIdsList", // return List<Long>
+                ParameterizedTypeName.get(List::class.typeName(), TypeName.LONG.box()))
+    }
+}
diff --git a/room/compiler/src/main/kotlin/android/arch/persistence/room/vo/Pojo.kt b/room/compiler/src/main/kotlin/android/arch/persistence/room/vo/Pojo.kt
new file mode 100644
index 0000000..acc63af
--- /dev/null
+++ b/room/compiler/src/main/kotlin/android/arch/persistence/room/vo/Pojo.kt
@@ -0,0 +1,33 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.arch.persistence.room.vo
+
+import android.arch.persistence.room.ext.typeName
+import com.squareup.javapoet.TypeName
+import javax.lang.model.element.ElementKind
+import javax.lang.model.element.TypeElement
+import javax.lang.model.type.DeclaredType
+
+/**
+ * A class is turned into a Pojo if it is used in a query response.
+ */
+// TODO make data class when move to kotlin 1.1
+open class Pojo(val element : TypeElement, val type: DeclaredType, val fields : List<Field>,
+                val embeddedFields: List<EmbeddedField>, val relations: List<Relation>,
+                val constructor : Constructor? = null) {
+    val typeName: TypeName by lazy { type.typeName() }
+}
diff --git a/room/compiler/src/main/kotlin/android/arch/persistence/room/vo/PrimaryKey.kt b/room/compiler/src/main/kotlin/android/arch/persistence/room/vo/PrimaryKey.kt
new file mode 100644
index 0000000..9735668
--- /dev/null
+++ b/room/compiler/src/main/kotlin/android/arch/persistence/room/vo/PrimaryKey.kt
@@ -0,0 +1,39 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package android.arch.persistence.room.vo
+
+import android.arch.persistence.room.migration.bundle.PrimaryKeyBundle
+import javax.lang.model.element.Element
+
+/**
+ * Represents a PrimaryKey for an Entity.
+ */
+data class PrimaryKey(val declaredIn : Element?, val fields: List<Field>,
+                      val autoGenerateId: Boolean) {
+    companion object {
+        val MISSING = PrimaryKey(null, emptyList(), false)
+    }
+
+    val columnNames by lazy { fields.map {it.columnName} }
+
+    fun toHumanReadableString(): String {
+        return "PrimaryKey[" +
+                fields.joinToString(separator = ", ", transform = Field::getPath) + "]"
+    }
+
+    fun toBundle(): PrimaryKeyBundle = PrimaryKeyBundle(
+            autoGenerateId, fields.map { it.columnName })
+}
diff --git a/room/compiler/src/main/kotlin/android/arch/persistence/room/vo/QueryMethod.kt b/room/compiler/src/main/kotlin/android/arch/persistence/room/vo/QueryMethod.kt
new file mode 100644
index 0000000..1aa8430
--- /dev/null
+++ b/room/compiler/src/main/kotlin/android/arch/persistence/room/vo/QueryMethod.kt
@@ -0,0 +1,51 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.arch.persistence.room.vo
+
+import android.arch.persistence.room.ext.typeName
+import android.arch.persistence.room.parser.ParsedQuery
+import android.arch.persistence.room.solver.query.result.QueryResultBinder
+import com.squareup.javapoet.TypeName
+import javax.lang.model.element.ExecutableElement
+import javax.lang.model.type.TypeMirror
+
+/**
+ * A class that holds information about a QueryMethod.
+ * It is self sufficient and must have all generics etc resolved once created.
+ */
+data class QueryMethod(val element: ExecutableElement, val query: ParsedQuery, val name: String,
+                       val returnType: TypeMirror, val parameters: List<QueryParameter>,
+                       val queryResultBinder : QueryResultBinder) {
+    val sectionToParamMapping by lazy {
+        query.bindSections.map {
+            if (it.text.trim() == "?") {
+                Pair(it, parameters.firstOrNull())
+            } else if (it.text.startsWith(":")) {
+                val subName = it.text.substring(1)
+                Pair(it, parameters.firstOrNull {
+                    it.name == subName
+                })
+            } else {
+                Pair(it, null)
+            }
+        }
+    }
+
+    val returnsValue by lazy {
+        returnType.typeName() != TypeName.VOID
+    }
+}
diff --git a/room/compiler/src/main/kotlin/android/arch/persistence/room/vo/QueryParameter.kt b/room/compiler/src/main/kotlin/android/arch/persistence/room/vo/QueryParameter.kt
new file mode 100644
index 0000000..60ab55e
--- /dev/null
+++ b/room/compiler/src/main/kotlin/android/arch/persistence/room/vo/QueryParameter.kt
@@ -0,0 +1,26 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.arch.persistence.room.vo
+
+import android.arch.persistence.room.solver.query.parameter.QueryParameterAdapter
+import javax.lang.model.type.TypeMirror
+
+/**
+ * Holds the parameter for a {@link QueryMethod}.
+ */
+data class QueryParameter(val name: String, val type: TypeMirror,
+                          val queryParamAdapter : QueryParameterAdapter?)
diff --git a/room/compiler/src/main/kotlin/android/arch/persistence/room/vo/Relation.kt b/room/compiler/src/main/kotlin/android/arch/persistence/room/vo/Relation.kt
new file mode 100644
index 0000000..940db31
--- /dev/null
+++ b/room/compiler/src/main/kotlin/android/arch/persistence/room/vo/Relation.kt
@@ -0,0 +1,53 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.arch.persistence.room.vo
+
+/**
+ * Value object created from processing a @Relation annotation.
+ */
+class Relation(
+        val entity: Entity,
+        val pojo: Pojo,
+        // field in Pojo that holds these relations (e.g. List<Pet> pets)
+        val field: Field,
+        // the parent field referenced for matching
+        val parentField: Field,
+        // the field referenced for querying. does not need to be in the response but the query
+        // we generate always has it in the response.
+        val entityField: Field,
+        // the projection for the query
+        val projection: List<String>) {
+
+    fun createLoadAllSql(): String {
+        var resultFields = if (projection.isNotEmpty()) {
+            projection
+        } else {
+            pojo.fields.map { it.columnName }
+        }
+        val entityFieldInResponse = pojo.fields.any { it.columnName == entityField.columnName }
+        if (!entityFieldInResponse) {
+            resultFields += entityField.columnName
+        }
+        return createSelect(resultFields)
+    }
+
+    private fun createSelect(resultFields: List<String>): String {
+        return "SELECT ${resultFields.joinToString(",")}" +
+                " FROM `${entity.tableName}`" +
+                " WHERE ${entityField.columnName} IN (:args)"
+    }
+}
diff --git a/room/compiler/src/main/kotlin/android/arch/persistence/room/vo/RelationCollector.kt b/room/compiler/src/main/kotlin/android/arch/persistence/room/vo/RelationCollector.kt
new file mode 100644
index 0000000..c11091c
--- /dev/null
+++ b/room/compiler/src/main/kotlin/android/arch/persistence/room/vo/RelationCollector.kt
@@ -0,0 +1,262 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.arch.persistence.room.vo
+
+import android.arch.persistence.room.ext.AndroidTypeNames
+import android.arch.persistence.room.ext.CommonTypeNames
+import android.arch.persistence.room.ext.L
+import android.arch.persistence.room.ext.N
+import android.arch.persistence.room.ext.T
+import android.arch.persistence.room.ext.typeName
+import android.arch.persistence.room.parser.ParsedQuery
+import android.arch.persistence.room.parser.SQLTypeAffinity
+import android.arch.persistence.room.parser.SqlParser
+import android.arch.persistence.room.processor.Context
+import android.arch.persistence.room.processor.ProcessorErrors.CANNOT_FIND_QUERY_RESULT_ADAPTER
+import android.arch.persistence.room.processor.ProcessorErrors.relationAffinityMismatch
+import android.arch.persistence.room.solver.CodeGenScope
+import android.arch.persistence.room.solver.query.result.RowAdapter
+import android.arch.persistence.room.solver.query.result.SingleColumnRowAdapter
+import android.arch.persistence.room.verifier.DatabaseVerificaitonErrors
+import android.arch.persistence.room.writer.QueryWriter
+import android.arch.persistence.room.writer.RelationCollectorMethodWriter
+import com.squareup.javapoet.ArrayTypeName
+import com.squareup.javapoet.ClassName
+import com.squareup.javapoet.CodeBlock
+import com.squareup.javapoet.ParameterizedTypeName
+import com.squareup.javapoet.TypeName
+import stripNonJava
+import java.util.ArrayList
+import java.util.HashSet
+import javax.lang.model.type.TypeKind
+import javax.lang.model.type.TypeMirror
+
+/**
+ * Internal class that is used to manage fetching 1/N to N relationships.
+ */
+data class RelationCollector(val relation: Relation,
+                             val affinity : SQLTypeAffinity,
+                             val mapTypeName: ParameterizedTypeName,
+                             val keyTypeName: TypeName,
+                             val collectionTypeName: ParameterizedTypeName,
+                             val queryWriter: QueryWriter,
+                             val rowAdapter: RowAdapter,
+                             val loadAllQuery : ParsedQuery) {
+    // set when writing the code generator in writeInitCode
+    lateinit var varName: String
+
+    fun writeInitCode(scope: CodeGenScope) {
+        val tmpVar = scope.getTmpVar(
+                "_collection${relation.field.getPath().stripNonJava().capitalize()}")
+        scope.builder().addStatement("final $T $L = new $T()", mapTypeName, tmpVar, mapTypeName)
+        varName = tmpVar
+    }
+
+    // called after reading each item to extract the key if it exists
+    fun writeReadParentKeyCode(cursorVarName: String, itemVar : String,
+                               fieldsWithIndices : List<FieldWithIndex>, scope: CodeGenScope) {
+        val indexVar = fieldsWithIndices.firstOrNull {
+            it.field === relation.parentField
+        }?.indexVar
+        scope.builder().apply {
+            readKey(
+                    cursorVarName = cursorVarName,
+                    indexVar = indexVar,
+                    scope = scope
+            ) { tmpVar ->
+                val tmpCollectionVar = scope.getTmpVar("_tmpCollection")
+                addStatement("$T $L = $L.get($L)", collectionTypeName, tmpCollectionVar,
+                        varName, tmpVar)
+                beginControlFlow("if($L == null)", tmpCollectionVar).apply {
+                    addStatement("$L = new $T()", tmpCollectionVar, collectionTypeName)
+                    addStatement("$L.put($L, $L)", varName, tmpVar, tmpCollectionVar)
+                }
+                endControlFlow()
+                // set it on the item
+                relation.field.setter.writeSet(itemVar, tmpCollectionVar, this)
+            }
+        }
+    }
+
+    fun writeCollectionCode(scope: CodeGenScope) {
+        val method = scope.writer
+                .getOrCreateMethod(RelationCollectorMethodWriter(this))
+        scope.builder().apply {
+            addStatement("$N($L)", method, varName)
+        }
+    }
+
+    fun readKey(cursorVarName: String, indexVar: String?, scope: CodeGenScope,
+                postRead: CodeBlock.Builder.(String) -> Unit) {
+        val cursorGetter = when (affinity) {
+            SQLTypeAffinity.INTEGER -> "getLong"
+            SQLTypeAffinity.REAL -> "getDouble"
+            SQLTypeAffinity.TEXT -> "getString"
+            SQLTypeAffinity.BLOB -> "getBlob"
+            else -> {
+                "getString"
+            }
+        }
+        scope.builder().apply {
+            beginControlFlow("if (!$L.isNull($L))", cursorVarName, indexVar).apply {
+                val tmpVar = scope.getTmpVar("_tmpKey")
+                addStatement("final $T $L = $L.$L($L)", keyTypeName,
+                        tmpVar, cursorVarName, cursorGetter, indexVar)
+                this.postRead(tmpVar)
+            }
+            endControlFlow()
+        }
+    }
+
+    companion object {
+        fun createCollectors(baseContext : Context, relations: List<Relation>)
+                : List<RelationCollector> {
+            return relations.map { relation ->
+                // decide on the affinity
+                val context = baseContext.fork(relation.field.element)
+                val parentAffinity = relation.parentField.cursorValueReader?.affinity()
+                val childAffinity = relation.entityField.cursorValueReader?.affinity()
+                val affinity = if (parentAffinity != null && parentAffinity == childAffinity) {
+                    parentAffinity
+                } else {
+                    context.logger.w(Warning.RELATION_TYPE_MISMATCH, relation.field.element,
+                            relationAffinityMismatch(
+                                    parentColumn = relation.parentField.columnName,
+                                    childColumn = relation.entityField.columnName,
+                                    parentAffinity = parentAffinity,
+                                    childAffinity = childAffinity))
+                    SQLTypeAffinity.TEXT
+                }
+                val keyType = keyTypeFor(context, affinity)
+                val collectionTypeName = if (relation.field.typeName is ParameterizedTypeName) {
+                    val paramType = relation.field.typeName as ParameterizedTypeName
+                    if (paramType.rawType == CommonTypeNames.LIST) {
+                        ParameterizedTypeName.get(ClassName.get(ArrayList::class.java),
+                                relation.pojo.typeName)
+                    } else if (paramType.rawType == CommonTypeNames.SET) {
+                        ParameterizedTypeName.get(ClassName.get(HashSet::class.java),
+                                relation.pojo.typeName)
+                    } else {
+                        ParameterizedTypeName.get(ClassName.get(ArrayList::class.java),
+                                relation.pojo.typeName)
+                    }
+                } else {
+                    ParameterizedTypeName.get(ClassName.get(ArrayList::class.java),
+                            relation.pojo.typeName)
+                }
+
+                val canUseArrayMap = context.processingEnv.elementUtils
+                        .getTypeElement(AndroidTypeNames.ARRAY_MAP.toString()) != null
+                val mapClass = if (canUseArrayMap) {
+                    AndroidTypeNames.ARRAY_MAP
+                } else {
+                    ClassName.get(java.util.HashMap::class.java)
+                }
+                val tmpMapType = ParameterizedTypeName.get(mapClass, keyType, collectionTypeName)
+                val keyTypeMirror = keyTypeMirrorFor(context, affinity)
+                val set = context.processingEnv.elementUtils.getTypeElement("java.util.Set")
+                val keySet = context.processingEnv.typeUtils.getDeclaredType(set, keyTypeMirror)
+                val loadAllQuery = relation.createLoadAllSql()
+                val parsedQuery = SqlParser.parse(loadAllQuery)
+                context.checker.check(parsedQuery.errors.isEmpty(), relation.field.element,
+                        parsedQuery.errors.joinToString("\n"))
+                if (parsedQuery.errors.isEmpty()) {
+                    val resultInfo = context.databaseVerifier?.analyze(loadAllQuery)
+                    parsedQuery.resultInfo = resultInfo
+                    if (resultInfo?.error != null) {
+                        context.logger.e(relation.field.element,
+                                DatabaseVerificaitonErrors.cannotVerifyQuery(resultInfo.error))
+                    }
+                }
+                val resultInfo = parsedQuery.resultInfo
+
+                val queryParam = QueryParameter(RelationCollectorMethodWriter.KEY_SET_VARIABLE,
+                        keySet, context.typeAdapterStore.findQueryParameterAdapter(keySet))
+                val queryWriter = QueryWriter(
+                        parameters = listOf(queryParam),
+                        sectionToParamMapping = listOf(Pair(parsedQuery.bindSections.first(),
+                                queryParam)),
+                        query = parsedQuery
+                )
+
+                // row adapter that matches full response
+                fun getDefaultRowAdapter() : RowAdapter? {
+                    return context.typeAdapterStore.findRowAdapter(relation.pojo.type, parsedQuery)
+                }
+                val rowAdapter = if (relation.projection.size == 1 && resultInfo != null &&
+                        (resultInfo.columns.size == 1 || resultInfo.columns.size == 2)) {
+                    // check for a column adapter first
+                    val cursorReader = context.typeAdapterStore.findCursorValueReader(
+                            relation.pojo.type, resultInfo.columns.first().type)
+                    if (cursorReader == null) {
+                        getDefaultRowAdapter()
+                    } else {
+                        context.logger.d("Choosing cursor adapter for the return value since" +
+                                " the query returns only 1 or 2 columns and there is a cursor" +
+                                " adapter for the return type.")
+                        SingleColumnRowAdapter(cursorReader)
+                    }
+                } else {
+                    getDefaultRowAdapter()
+                }
+
+                if (rowAdapter == null) {
+                    context.logger.e(relation.field.element, CANNOT_FIND_QUERY_RESULT_ADAPTER)
+                    null
+                } else {
+                    RelationCollector(
+                            relation = relation,
+                            affinity = affinity,
+                            mapTypeName = tmpMapType,
+                            keyTypeName = keyType,
+                            collectionTypeName = collectionTypeName,
+                            queryWriter = queryWriter,
+                            rowAdapter = rowAdapter,
+                            loadAllQuery = parsedQuery
+                    )
+                }
+            }.filterNotNull()
+        }
+
+        private fun keyTypeMirrorFor(context: Context, affinity: SQLTypeAffinity): TypeMirror {
+            val types = context.processingEnv.typeUtils
+            val elements = context.processingEnv.elementUtils
+            return when (affinity) {
+                SQLTypeAffinity.INTEGER -> elements.getTypeElement("java.lang.Long").asType()
+                SQLTypeAffinity.REAL -> elements.getTypeElement("java.lang.Double").asType()
+                SQLTypeAffinity.TEXT -> context.COMMON_TYPES.STRING
+                SQLTypeAffinity.BLOB -> types.getArrayType(types.getPrimitiveType(TypeKind.BYTE))
+                else -> {
+                    context.COMMON_TYPES.STRING
+                }
+            }
+        }
+
+        private fun keyTypeFor(context : Context, affinity: SQLTypeAffinity): TypeName {
+            return when (affinity) {
+                SQLTypeAffinity.INTEGER -> TypeName.LONG.box()
+                SQLTypeAffinity.REAL -> TypeName.DOUBLE.box()
+                SQLTypeAffinity.TEXT -> TypeName.get(String::class.java)
+                SQLTypeAffinity.BLOB -> ArrayTypeName.of(TypeName.BYTE)
+                else -> {
+                    // no affinity select from type
+                    context.COMMON_TYPES.STRING.typeName()
+                }
+            }
+        }
+    }
+}
diff --git a/room/compiler/src/main/kotlin/android/arch/persistence/room/vo/ShortcutMethod.kt b/room/compiler/src/main/kotlin/android/arch/persistence/room/vo/ShortcutMethod.kt
new file mode 100644
index 0000000..121f993
--- /dev/null
+++ b/room/compiler/src/main/kotlin/android/arch/persistence/room/vo/ShortcutMethod.kt
@@ -0,0 +1,26 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.arch.persistence.room.vo
+
+import javax.lang.model.element.ExecutableElement
+
+/**
+ * Base class for shortcut methods in @DAO.
+ */
+abstract class ShortcutMethod(val element: ExecutableElement, val name: String,
+                              val entities: Map<String, Entity>, val returnCount: Boolean,
+                              val parameters: List<ShortcutQueryParameter>)
diff --git a/room/compiler/src/main/kotlin/android/arch/persistence/room/vo/ShortcutQueryParameter.kt b/room/compiler/src/main/kotlin/android/arch/persistence/room/vo/ShortcutQueryParameter.kt
new file mode 100644
index 0000000..29fc759
--- /dev/null
+++ b/room/compiler/src/main/kotlin/android/arch/persistence/room/vo/ShortcutQueryParameter.kt
@@ -0,0 +1,36 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.arch.persistence.room.vo
+
+import javax.lang.model.type.TypeMirror
+
+/**
+ * Parameters used in DAO methods that are annotated with Insert, Delete, Update.
+ */
+data class ShortcutQueryParameter(val name: String, val type: TypeMirror,
+                                  val entityType: TypeMirror?, val isMultiple: Boolean) {
+    /**
+     * Method name in entity insertion or update adapter.
+     */
+    fun handleMethodName() : String {
+        return if (isMultiple) {
+            "handleMultiple"
+        } else {
+            "handle"
+        }
+    }
+}
diff --git a/room/compiler/src/main/kotlin/android/arch/persistence/room/vo/UpdateMethod.kt b/room/compiler/src/main/kotlin/android/arch/persistence/room/vo/UpdateMethod.kt
new file mode 100644
index 0000000..ed8eb8b
--- /dev/null
+++ b/room/compiler/src/main/kotlin/android/arch/persistence/room/vo/UpdateMethod.kt
@@ -0,0 +1,26 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.arch.persistence.room.vo
+
+import android.arch.persistence.room.OnConflictStrategy
+import javax.lang.model.element.ExecutableElement
+
+class UpdateMethod(element: ExecutableElement, name: String,
+                   entities: Map<String, Entity>, returnCount: Boolean,
+                   parameters: List<ShortcutQueryParameter>,
+                   @OnConflictStrategy val onConflictStrategy: Int) : ShortcutMethod(
+        element, name, entities, returnCount, parameters)
diff --git a/room/compiler/src/main/kotlin/android/arch/persistence/room/vo/Warning.kt b/room/compiler/src/main/kotlin/android/arch/persistence/room/vo/Warning.kt
new file mode 100644
index 0000000..9cf137d
--- /dev/null
+++ b/room/compiler/src/main/kotlin/android/arch/persistence/room/vo/Warning.kt
@@ -0,0 +1,42 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.arch.persistence.room.vo
+
+/**
+ * Internal representation of supported warnings
+ */
+enum class Warning(val publicKey: String) {
+    ALL("ALL"),
+    CURSOR_MISMATCH("ROOM_CURSOR_MISMATCH"),
+    MISSING_JAVA_TMP_DIR("ROOM_MISSING_JAVA_TMP_DIR"),
+    CANNOT_CREATE_VERIFICATION_DATABASE("ROOM_CANNOT_CREATE_VERIFICATION_DATABASE"),
+    PRIMARY_KEY_FROM_EMBEDDED_IS_DROPPED("ROOM_EMBEDDED_PRIMARY_KEY_IS_DROPPED"),
+    INDEX_FROM_EMBEDDED_FIELD_IS_DROPPED("ROOM_EMBEDDED_INDEX_IS_DROPPED"),
+    INDEX_FROM_EMBEDDED_ENTITY_IS_DROPPED("ROOM_EMBEDDED_ENTITY_INDEX_IS_DROPPED"),
+    INDEX_FROM_PARENT_IS_DROPPED("ROOM_PARENT_INDEX_IS_DROPPED"),
+    INDEX_FROM_PARENT_FIELD_IS_DROPPED("ROOM_PARENT_FIELD_INDEX_IS_DROPPED"),
+    RELATION_TYPE_MISMATCH("ROOM_RELATION_TYPE_MISMATCH"),
+    MISSING_SCHEMA_LOCATION("ROOM_MISSING_SCHEMA_LOCATION"),
+    MISSING_INDEX_ON_FOREIGN_KEY_CHILD("ROOM_MISSING_FOREIGN_KEY_CHILD_INDEX");
+
+    companion object {
+        val PUBLIC_KEY_MAP = Warning.values().associateBy { it.publicKey }
+        fun fromPublicKey(publicKey: String): Warning? {
+            return PUBLIC_KEY_MAP[publicKey.toUpperCase()]
+        }
+    }
+}
diff --git a/room/compiler/src/main/kotlin/android/arch/persistence/room/writer/ClassWriter.kt b/room/compiler/src/main/kotlin/android/arch/persistence/room/writer/ClassWriter.kt
new file mode 100644
index 0000000..7f386bd
--- /dev/null
+++ b/room/compiler/src/main/kotlin/android/arch/persistence/room/writer/ClassWriter.kt
@@ -0,0 +1,100 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.arch.persistence.room.writer
+
+import android.arch.persistence.room.solver.CodeGenScope.Companion.CLASS_PROPERTY_PREFIX
+import com.squareup.javapoet.ClassName
+import com.squareup.javapoet.FieldSpec
+import com.squareup.javapoet.JavaFile
+import com.squareup.javapoet.MethodSpec
+import com.squareup.javapoet.TypeName
+import com.squareup.javapoet.TypeSpec
+import javax.annotation.processing.ProcessingEnvironment
+
+/**
+ * Base class for all writers that can produce a class.
+ */
+abstract class ClassWriter(val className: ClassName) {
+    private val sharedFieldSpecs = mutableMapOf<String, FieldSpec>()
+    private val sharedMethodSpecs = mutableMapOf<String, MethodSpec>()
+    private val sharedFieldNames = mutableSetOf<String>()
+    private val sharedMethodNames = mutableSetOf<String>()
+
+    abstract fun createTypeSpecBuilder(): TypeSpec.Builder
+
+    fun write(processingEnv: ProcessingEnvironment) {
+        val builder = createTypeSpecBuilder()
+        sharedFieldSpecs.values.forEach { builder.addField(it) }
+        sharedMethodSpecs.values.forEach { builder.addMethod(it) }
+        JavaFile.builder(className.packageName(), builder.build())
+                .build()
+                .writeTo(processingEnv.filer)
+    }
+
+    private fun makeUnique(set: MutableSet<String>, value: String): String {
+        if (!value.startsWith(CLASS_PROPERTY_PREFIX)) {
+            return makeUnique(set, "$CLASS_PROPERTY_PREFIX$value")
+        }
+        if (set.add(value)) {
+            return value
+        }
+        var index = 1
+        while (true) {
+            if (set.add("${value}_$index")) {
+                return "${value}_$index"
+            }
+            index++
+        }
+    }
+
+    fun getOrCreateField(sharedField: SharedFieldSpec): FieldSpec {
+        return sharedFieldSpecs.getOrPut(sharedField.getUniqueKey(), {
+            sharedField.build(this, makeUnique(sharedFieldNames, sharedField.baseName))
+        })
+    }
+
+    fun getOrCreateMethod(sharedMethod: SharedMethodSpec): MethodSpec {
+        return sharedMethodSpecs.getOrPut(sharedMethod.getUniqueKey(), {
+            sharedMethod.build(this, makeUnique(sharedMethodNames, sharedMethod.baseName))
+        })
+    }
+
+    abstract class SharedFieldSpec(val baseName: String, val type: TypeName) {
+
+        abstract fun getUniqueKey(): String
+
+        abstract fun prepare(writer: ClassWriter, builder: FieldSpec.Builder)
+
+        fun build(classWriter: ClassWriter, name: String): FieldSpec {
+            val builder = FieldSpec.builder(type, name)
+            prepare(classWriter, builder)
+            return builder.build()
+        }
+    }
+
+    abstract class SharedMethodSpec(val baseName: String) {
+
+        abstract fun getUniqueKey(): String
+        abstract fun prepare(writer: ClassWriter, builder: MethodSpec.Builder)
+
+        fun build(writer: ClassWriter, name: String): MethodSpec {
+            val builder = MethodSpec.methodBuilder(name)
+            prepare(writer, builder)
+            return builder.build()
+        }
+    }
+}
diff --git a/room/compiler/src/main/kotlin/android/arch/persistence/room/writer/DaoWriter.kt b/room/compiler/src/main/kotlin/android/arch/persistence/room/writer/DaoWriter.kt
new file mode 100644
index 0000000..26d41e4
--- /dev/null
+++ b/room/compiler/src/main/kotlin/android/arch/persistence/room/writer/DaoWriter.kt
@@ -0,0 +1,445 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.arch.persistence.room.writer
+
+import android.arch.persistence.room.ext.L
+import android.arch.persistence.room.ext.N
+import android.arch.persistence.room.ext.RoomTypeNames
+import android.arch.persistence.room.ext.SupportDbTypeNames
+import android.arch.persistence.room.ext.T
+import android.arch.persistence.room.ext.typeName
+import android.arch.persistence.room.parser.QueryType
+import android.arch.persistence.room.processor.OnConflictProcessor
+import android.arch.persistence.room.solver.CodeGenScope
+import android.arch.persistence.room.vo.Dao
+import android.arch.persistence.room.vo.Entity
+import android.arch.persistence.room.vo.InsertionMethod
+import android.arch.persistence.room.vo.QueryMethod
+import android.arch.persistence.room.vo.ShortcutMethod
+import com.google.auto.common.MoreTypes
+import com.squareup.javapoet.ClassName
+import com.squareup.javapoet.CodeBlock
+import com.squareup.javapoet.FieldSpec
+import com.squareup.javapoet.MethodSpec
+import com.squareup.javapoet.ParameterSpec
+import com.squareup.javapoet.TypeName
+import com.squareup.javapoet.TypeSpec
+import stripNonJava
+import javax.annotation.processing.ProcessingEnvironment
+import javax.lang.model.element.ElementKind
+import javax.lang.model.element.ExecutableElement
+import javax.lang.model.element.Modifier.FINAL
+import javax.lang.model.element.Modifier.PRIVATE
+import javax.lang.model.element.Modifier.PUBLIC
+import javax.lang.model.type.DeclaredType
+
+/**
+ * Creates the implementation for a class annotated with Dao.
+ */
+class DaoWriter(val dao: Dao, val processingEnv: ProcessingEnvironment)
+    : ClassWriter(dao.typeName) {
+    val declaredDao = MoreTypes.asDeclared(dao.element.asType())
+    companion object {
+        // TODO nothing prevents this from conflicting, we should fix.
+        val dbField: FieldSpec = FieldSpec
+                .builder(RoomTypeNames.ROOM_DB, "__db", PRIVATE, FINAL)
+                .build()
+
+        private fun typeNameToFieldName(typeName: TypeName?): String {
+            if (typeName is ClassName) {
+                return typeName.simpleName()
+            } else {
+                return typeName.toString().replace('.', '_').stripNonJava()
+            }
+        }
+    }
+
+    override fun createTypeSpecBuilder(): TypeSpec.Builder {
+        val builder = TypeSpec.classBuilder(dao.implTypeName)
+        /**
+         * if delete / update query method wants to return modified rows, we need prepared query.
+         * in that case, if args are dynamic, we cannot re-use the query, if not, we should re-use
+         * it. this requires more work but creates good performance.
+         */
+        val groupedDeleteUpdate = dao.queryMethods
+                .filter { it.query.type == QueryType.DELETE || it.query.type == QueryType.UPDATE }
+                .groupBy { it.parameters.any { it.queryParamAdapter?.isMultiple ?: true } }
+        // delete queries that can be prepared ahead of time
+        val preparedDeleteOrUpdateQueries = groupedDeleteUpdate[false] ?: emptyList()
+        // delete queries that must be rebuild every single time
+        val oneOffDeleteOrUpdateQueries = groupedDeleteUpdate[true] ?: emptyList()
+        val shortcutMethods = createInsertionMethods() +
+                createDeletionMethods() + createUpdateMethods() +
+                createPreparedDeleteOrUpdateQueries(preparedDeleteOrUpdateQueries)
+
+        builder.apply {
+            addModifiers(PUBLIC)
+            if (dao.element.kind == ElementKind.INTERFACE) {
+                addSuperinterface(dao.typeName)
+            } else {
+                superclass(dao.typeName)
+            }
+            addField(dbField)
+            val dbParam = ParameterSpec
+                    .builder(dao.constructorParamType ?: dbField.type, dbField.name).build()
+
+            addMethod(createConstructor(dbParam, shortcutMethods, dao.constructorParamType != null))
+
+            shortcutMethods.forEach {
+                addMethod(it.methodImpl)
+            }
+
+            dao.queryMethods.filter { it.query.type == QueryType.SELECT }.forEach { method ->
+                addMethod(createSelectMethod(method))
+            }
+            oneOffDeleteOrUpdateQueries.forEach {
+                addMethod(createDeleteOrUpdateQueryMethod(it))
+            }
+        }
+        return builder
+    }
+
+    private fun createPreparedDeleteOrUpdateQueries(preparedDeleteQueries: List<QueryMethod>)
+            : List<PreparedStmtQuery> {
+        return preparedDeleteQueries.map { method ->
+            val fieldSpec = getOrCreateField(PreparedStatementField(method))
+            val queryWriter = QueryWriter(method)
+            val fieldImpl = PreparedStatementWriter(queryWriter)
+                    .createAnonymous(this@DaoWriter, dbField)
+            val methodBody = createPreparedDeleteQueryMethodBody(method, fieldSpec, queryWriter)
+            PreparedStmtQuery(mapOf(method.parameters.first().name
+                    to (fieldSpec to fieldImpl)), methodBody)
+        }
+    }
+
+    private fun createPreparedDeleteQueryMethodBody(method: QueryMethod,
+                                                    preparedStmtField: FieldSpec,
+                                                    queryWriter: QueryWriter): MethodSpec {
+        val scope = CodeGenScope(this)
+        val methodBuilder = overrideWithoutAnnotations(method.element, declaredDao).apply {
+            val stmtName = scope.getTmpVar("_stmt")
+            addStatement("final $T $L = $N.acquire()",
+                    SupportDbTypeNames.SQLITE_STMT, stmtName, preparedStmtField)
+            addStatement("$N.beginTransaction()", dbField)
+            beginControlFlow("try").apply {
+                val bindScope = scope.fork()
+                queryWriter.bindArgs(stmtName, emptyList(), bindScope)
+                addCode(bindScope.builder().build())
+                if (method.returnsValue) {
+                    val resultVar = scope.getTmpVar("_result")
+                    addStatement("final $L $L = $L.executeUpdateDelete()",
+                            method.returnType.typeName(), resultVar, stmtName)
+                    addStatement("$N.setTransactionSuccessful()", dbField)
+                    addStatement("return $L", resultVar)
+                } else {
+                    addStatement("$L.executeUpdateDelete()", stmtName)
+                    addStatement("$N.setTransactionSuccessful()", dbField)
+                }
+            }
+            nextControlFlow("finally").apply {
+                addStatement("$N.endTransaction()", dbField)
+                addStatement("$N.release($L)", preparedStmtField, stmtName)
+            }
+            endControlFlow()
+        }
+        return methodBuilder.build()
+    }
+
+    private fun createConstructor(dbParam: ParameterSpec,
+                                  shortcutMethods: List<PreparedStmtQuery>,
+                                  callSuper: Boolean): MethodSpec {
+        return MethodSpec.constructorBuilder().apply {
+            addParameter(dbParam)
+            addModifiers(PUBLIC)
+            if (callSuper) {
+                addStatement("super($N)", dbParam)
+            }
+            addStatement("this.$N = $N", dbField, dbParam)
+            shortcutMethods.filterNot {
+                it.fields.isEmpty()
+            }.map {
+                it.fields.values
+            }.flatten().groupBy {
+                it.first.name
+            }.map {
+                it.value.first()
+            }.forEach {
+                addStatement("this.$N = $L", it.first, it.second)
+            }
+        }.build()
+    }
+
+    private fun createSelectMethod(method: QueryMethod): MethodSpec {
+        return overrideWithoutAnnotations(method.element, declaredDao).apply {
+            addCode(createQueryMethodBody(method))
+        }.build()
+    }
+
+    private fun createDeleteOrUpdateQueryMethod(method: QueryMethod): MethodSpec {
+        return overrideWithoutAnnotations(method.element, declaredDao).apply {
+            addCode(createDeleteOrUpdateQueryMethodBody(method))
+        }.build()
+    }
+
+    /**
+     * Groups all insertion methods based on the insert statement they will use then creates all
+     * field specs, EntityInsertionAdapterWriter and actual insert methods.
+     */
+    private fun createInsertionMethods(): List<PreparedStmtQuery> {
+        return dao.insertionMethods
+                .map { insertionMethod ->
+                    val onConflict = OnConflictProcessor.onConflictText(insertionMethod.onConflict)
+                    val entities = insertionMethod.entities
+
+                    val fields = entities.mapValues {
+                        val spec = getOrCreateField(InsertionMethodField(it.value, onConflict))
+                        val impl = EntityInsertionAdapterWriter(it.value, onConflict)
+                                .createAnonymous(this@DaoWriter, dbField.name)
+                        spec to impl
+                    }
+                    val methodImpl = overrideWithoutAnnotations(insertionMethod.element,
+                            declaredDao).apply {
+                        addCode(createInsertionMethodBody(insertionMethod, fields))
+                    }.build()
+                    PreparedStmtQuery(fields, methodImpl)
+                }.filterNotNull()
+    }
+
+    private fun createInsertionMethodBody(method: InsertionMethod,
+                                          insertionAdapters: Map<String, Pair<FieldSpec, TypeSpec>>)
+            : CodeBlock {
+        val insertionType = method.insertionType
+        if (insertionAdapters.isEmpty() || insertionType == null) {
+            return CodeBlock.builder().build()
+        }
+        val scope = CodeGenScope(this)
+
+        return scope.builder().apply {
+            // TODO assert thread
+            // TODO collect results
+            addStatement("$N.beginTransaction()", dbField)
+            val needsReturnType = insertionType != InsertionMethod.Type.INSERT_VOID
+            val resultVar = if (needsReturnType) {
+                scope.getTmpVar("_result")
+            } else {
+                null
+            }
+
+            beginControlFlow("try").apply {
+                method.parameters.forEach { param ->
+                    val insertionAdapter = insertionAdapters[param.name]?.first
+                    if (needsReturnType) {
+                        // if it has more than 1 parameter, we would've already printed the error
+                        // so we don't care about re-declaring the variable here
+                        addStatement("$T $L = $N.$L($L)",
+                                insertionType.returnTypeName, resultVar,
+                                insertionAdapter, insertionType.methodName,
+                                param.name)
+                    } else {
+                        addStatement("$N.$L($L)", insertionAdapter, insertionType.methodName,
+                                param.name)
+                    }
+                }
+                addStatement("$N.setTransactionSuccessful()", dbField)
+                if (needsReturnType) {
+                    addStatement("return $L", resultVar)
+                }
+            }
+            nextControlFlow("finally").apply {
+                addStatement("$N.endTransaction()", dbField)
+            }
+            endControlFlow()
+        }.build()
+    }
+
+    /**
+     * Creates EntityUpdateAdapter for each deletion method.
+     */
+    private fun createDeletionMethods(): List<PreparedStmtQuery> {
+        return createShortcutMethods(dao.deletionMethods, "deletion", { deletionMethod, entity ->
+            EntityDeletionAdapterWriter(entity)
+                    .createAnonymous(this@DaoWriter, dbField.name)
+        })
+    }
+
+    /**
+     * Creates EntityUpdateAdapter for each @Update method.
+     */
+    private fun createUpdateMethods(): List<PreparedStmtQuery> {
+        return createShortcutMethods(dao.updateMethods, "update", { update, entity ->
+            val onConflict = OnConflictProcessor.onConflictText(update.onConflictStrategy)
+            EntityUpdateAdapterWriter(entity, onConflict)
+                    .createAnonymous(this@DaoWriter, dbField.name)
+        })
+    }
+
+    private fun <T : ShortcutMethod> createShortcutMethods(methods: List<T>, methodPrefix: String,
+                                                           implCallback: (T, Entity) -> TypeSpec)
+            : List<PreparedStmtQuery> {
+        return methods.map { method ->
+            val entities = method.entities
+
+            if (entities.isEmpty()) {
+                null
+            } else {
+                val fields = entities.mapValues {
+                    val spec = getOrCreateField(DeleteOrUpdateAdapterField(it.value, methodPrefix))
+                    val impl = implCallback(method, it.value)
+                    spec to impl
+                }
+                val methodSpec = overrideWithoutAnnotations(method.element, declaredDao).apply {
+                    addCode(createDeleteOrUpdateMethodBody(method, fields))
+                }.build()
+                PreparedStmtQuery(fields, methodSpec)
+            }
+        }.filterNotNull()
+    }
+
+    private fun createDeleteOrUpdateMethodBody(method: ShortcutMethod,
+                                               adapters: Map<String, Pair<FieldSpec, TypeSpec>>)
+            : CodeBlock {
+        if (adapters.isEmpty()) {
+            return CodeBlock.builder().build()
+        }
+        val scope = CodeGenScope(this)
+        val resultVar = if (method.returnCount) {
+            scope.getTmpVar("_total")
+        } else {
+            null
+        }
+        return scope.builder().apply {
+            if (resultVar != null) {
+                addStatement("$T $L = 0", TypeName.INT, resultVar)
+            }
+            addStatement("$N.beginTransaction()", dbField)
+            beginControlFlow("try").apply {
+                method.parameters.forEach { param ->
+                    val adapter = adapters[param.name]?.first
+                    addStatement("$L$N.$L($L)",
+                            if (resultVar == null) "" else "$resultVar +=",
+                            adapter, param.handleMethodName(), param.name)
+                }
+                addStatement("$N.setTransactionSuccessful()", dbField)
+                if (resultVar != null) {
+                    addStatement("return $L", resultVar)
+                }
+            }
+            nextControlFlow("finally").apply {
+                addStatement("$N.endTransaction()", dbField)
+            }
+            endControlFlow()
+        }.build()
+    }
+
+    /**
+     * @Query with delete action
+     */
+    private fun createDeleteOrUpdateQueryMethodBody(method: QueryMethod): CodeBlock {
+        val queryWriter = QueryWriter(method)
+        val scope = CodeGenScope(this)
+        val sqlVar = scope.getTmpVar("_sql")
+        val stmtVar = scope.getTmpVar("_stmt")
+        queryWriter.prepareQuery(sqlVar, scope)
+        scope.builder().apply {
+            addStatement("$T $L = $N.compileStatement($L)",
+                    SupportDbTypeNames.SQLITE_STMT, stmtVar, dbField, sqlVar)
+            queryWriter.bindArgs(stmtVar, emptyList(), scope)
+            addStatement("$N.beginTransaction()", dbField)
+            beginControlFlow("try").apply {
+                if (method.returnsValue) {
+                    val resultVar = scope.getTmpVar("_result")
+                    addStatement("final $L $L = $L.executeUpdateDelete()",
+                            method.returnType.typeName(), resultVar, stmtVar)
+                    addStatement("$N.setTransactionSuccessful()", dbField)
+                    addStatement("return $L", resultVar)
+                } else {
+                    addStatement("$L.executeUpdateDelete()", stmtVar)
+                    addStatement("$N.setTransactionSuccessful()", dbField)
+                }
+            }
+            nextControlFlow("finally").apply {
+                addStatement("$N.endTransaction()", dbField)
+            }
+            endControlFlow()
+
+        }
+        return scope.builder().build()
+    }
+
+    private fun createQueryMethodBody(method: QueryMethod): CodeBlock {
+        val queryWriter = QueryWriter(method)
+        val scope = CodeGenScope(this)
+        val sqlVar = scope.getTmpVar("_sql")
+        val roomSQLiteQueryVar = scope.getTmpVar("_statement")
+        queryWriter.prepareReadAndBind(sqlVar, roomSQLiteQueryVar, scope)
+        method.queryResultBinder.convertAndReturn(roomSQLiteQueryVar, dbField, scope)
+        return scope.builder().build()
+    }
+
+    private fun overrideWithoutAnnotations(elm: ExecutableElement,
+                                           owner : DeclaredType): MethodSpec.Builder {
+        val baseSpec = MethodSpec.overriding(elm, owner, processingEnv.typeUtils).build()
+        return MethodSpec.methodBuilder(baseSpec.name).apply {
+            addAnnotation(Override::class.java)
+            addModifiers(baseSpec.modifiers)
+            addParameters(baseSpec.parameters)
+            varargs(baseSpec.varargs)
+            returns(baseSpec.returnType)
+        }
+    }
+
+    data class PreparedStmtQuery(val fields: Map<String, Pair<FieldSpec, TypeSpec>>,
+                                 val methodImpl: MethodSpec)
+
+    private class InsertionMethodField(val entity: Entity, val onConflictText: String)
+        : SharedFieldSpec(
+            "insertionAdapterOf${Companion.typeNameToFieldName(entity.typeName)}",
+            RoomTypeNames.INSERTION_ADAPTER) {
+
+        override fun getUniqueKey(): String {
+            return "${entity.typeName} $onConflictText"
+        }
+
+        override fun prepare(writer: ClassWriter, builder: FieldSpec.Builder) {
+            builder.addModifiers(FINAL, PRIVATE)
+        }
+    }
+
+    class DeleteOrUpdateAdapterField(val entity: Entity, val methodPrefix: String)
+        : SharedFieldSpec(
+            "${methodPrefix}AdapterOf${Companion.typeNameToFieldName(entity.typeName)}",
+            RoomTypeNames.DELETE_OR_UPDATE_ADAPTER) {
+        override fun prepare(writer: ClassWriter, builder: FieldSpec.Builder) {
+            builder.addModifiers(PRIVATE, FINAL)
+        }
+
+        override fun getUniqueKey(): String {
+            return entity.typeName.toString() + methodPrefix
+        }
+    }
+
+    class PreparedStatementField(val method: QueryMethod) : SharedFieldSpec(
+            "preparedStmtOf${method.name.capitalize()}", RoomTypeNames.SHARED_SQLITE_STMT) {
+        override fun prepare(writer: ClassWriter, builder: FieldSpec.Builder) {
+            builder.addModifiers(PRIVATE, FINAL)
+        }
+
+        override fun getUniqueKey(): String {
+            return method.query.original
+        }
+    }
+}
diff --git a/room/compiler/src/main/kotlin/android/arch/persistence/room/writer/DatabaseWriter.kt b/room/compiler/src/main/kotlin/android/arch/persistence/room/writer/DatabaseWriter.kt
new file mode 100644
index 0000000..e40bd71
--- /dev/null
+++ b/room/compiler/src/main/kotlin/android/arch/persistence/room/writer/DatabaseWriter.kt
@@ -0,0 +1,118 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.arch.persistence.room.writer
+
+import android.arch.persistence.room.ext.L
+import android.arch.persistence.room.ext.N
+import android.arch.persistence.room.ext.RoomTypeNames
+import android.arch.persistence.room.ext.SupportDbTypeNames
+import android.arch.persistence.room.ext.T
+import android.arch.persistence.room.solver.CodeGenScope
+import android.arch.persistence.room.vo.DaoMethod
+import android.arch.persistence.room.vo.Database
+import com.google.auto.common.MoreElements
+import com.squareup.javapoet.FieldSpec
+import com.squareup.javapoet.MethodSpec
+import com.squareup.javapoet.ParameterSpec
+import com.squareup.javapoet.TypeSpec
+import stripNonJava
+import javax.lang.model.element.Modifier
+import javax.lang.model.element.Modifier.PRIVATE
+import javax.lang.model.element.Modifier.PROTECTED
+import javax.lang.model.element.Modifier.PUBLIC
+import javax.lang.model.element.Modifier.VOLATILE
+
+/**
+ * Writes implementation of classes that were annotated with @Database.
+ */
+class DatabaseWriter(val database : Database) : ClassWriter(database.implTypeName) {
+    override fun createTypeSpecBuilder(): TypeSpec.Builder {
+        val builder = TypeSpec.classBuilder(database.implTypeName)
+        builder.apply {
+            addModifiers(PUBLIC)
+            superclass(database.typeName)
+            addMethod(createCreateOpenHelper())
+            addMethod(createCreateInvalidationTracker())
+        }
+        addDaoImpls(builder)
+        return builder
+    }
+
+    private fun createCreateInvalidationTracker(): MethodSpec {
+        return MethodSpec.methodBuilder("createInvalidationTracker").apply {
+            addAnnotation(Override::class.java)
+            addModifiers(PROTECTED)
+            returns(RoomTypeNames.INVALIDATION_TRACKER)
+            val tableNames = database.entities.joinToString(",") {
+                "\"${it.tableName}\""
+            }
+            addStatement("return new $T(this, $L)", RoomTypeNames.INVALIDATION_TRACKER, tableNames)
+        }.build()
+    }
+
+    private fun  addDaoImpls(builder: TypeSpec.Builder) {
+        val scope = CodeGenScope(this)
+        builder.apply {
+            database.daoMethods.forEach { method ->
+                val name = method.dao.typeName.simpleName().decapitalize().stripNonJava()
+                val fieldName = scope.getTmpVar("_$name")
+                val field = FieldSpec.builder(method.dao.typeName, fieldName,
+                        PRIVATE, VOLATILE).build()
+                addField(field)
+                addMethod(createDaoGetter(field, method))
+            }
+        }
+    }
+
+    private fun createDaoGetter(field: FieldSpec, method: DaoMethod) : MethodSpec {
+        return MethodSpec.overriding(MoreElements.asExecutable(method.element)).apply {
+            beginControlFlow("if ($N != null)", field).apply {
+                addStatement("return $N", field)
+            }
+            nextControlFlow("else").apply {
+                beginControlFlow("synchronized(this)").apply {
+                    beginControlFlow("if($N == null)", field).apply {
+                        addStatement("$N = new $T(this)", field, method.dao.implTypeName)
+                    }
+                    endControlFlow()
+                    addStatement("return $N", field)
+                }
+                endControlFlow()
+            }
+            endControlFlow()
+        }.build()
+    }
+
+    private fun createCreateOpenHelper() : MethodSpec {
+        val scope = CodeGenScope(this)
+        return MethodSpec.methodBuilder("createOpenHelper").apply {
+            addModifiers(Modifier.PROTECTED)
+            returns(SupportDbTypeNames.SQLITE_OPEN_HELPER)
+
+            val configParam = ParameterSpec.builder(RoomTypeNames.ROOM_DB_CONFIG,
+                    "configuration").build()
+            addParameter(configParam)
+
+            val openHelperVar = scope.getTmpVar("_helper")
+            val openHelperCode = scope.fork()
+            SQLiteOpenHelperWriter(database)
+                    .write(openHelperVar, configParam, openHelperCode)
+            addCode(openHelperCode.builder().build())
+            addStatement("return $L", openHelperVar)
+        }.build()
+    }
+}
diff --git a/room/compiler/src/main/kotlin/android/arch/persistence/room/writer/EntityCursorConverterWriter.kt b/room/compiler/src/main/kotlin/android/arch/persistence/room/writer/EntityCursorConverterWriter.kt
new file mode 100644
index 0000000..1a8af7e
--- /dev/null
+++ b/room/compiler/src/main/kotlin/android/arch/persistence/room/writer/EntityCursorConverterWriter.kt
@@ -0,0 +1,86 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.arch.persistence.room.writer
+
+import android.arch.persistence.room.ext.AndroidTypeNames
+import android.arch.persistence.room.ext.L
+import android.arch.persistence.room.ext.N
+import android.arch.persistence.room.ext.S
+import android.arch.persistence.room.ext.T
+import android.arch.persistence.room.solver.CodeGenScope
+import android.arch.persistence.room.vo.Entity
+import android.arch.persistence.room.vo.Field
+import android.arch.persistence.room.vo.EmbeddedField
+import android.arch.persistence.room.vo.FieldWithIndex
+import com.squareup.javapoet.CodeBlock
+import com.squareup.javapoet.MethodSpec
+import com.squareup.javapoet.ParameterSpec
+import com.squareup.javapoet.TypeName
+import stripNonJava
+import javax.lang.model.element.Modifier.PRIVATE
+
+class EntityCursorConverterWriter(val entity: Entity) : ClassWriter.SharedMethodSpec(
+        "entityCursorConverter_${entity.typeName.toString().stripNonJava()}") {
+    override fun getUniqueKey(): String {
+        return "generic_entity_converter_of_${entity.element.qualifiedName}"
+    }
+
+    override fun prepare(writer: ClassWriter, builder: MethodSpec.Builder) {
+        builder.apply {
+            val cursorParam = ParameterSpec
+                    .builder(AndroidTypeNames.CURSOR, "cursor").build()
+            addParameter(cursorParam)
+            addModifiers(PRIVATE)
+            returns(entity.typeName)
+            addCode(buildConvertMethodBody(writer, cursorParam))
+        }
+    }
+
+    private fun depth(parent: EmbeddedField?): Int {
+        return if (parent == null) {
+            0
+        } else {
+            1 + depth(parent.parent)
+        }
+    }
+
+    private fun buildConvertMethodBody(writer: ClassWriter, cursorParam: ParameterSpec)
+            : CodeBlock {
+        val scope = CodeGenScope(writer)
+        val entityVar = scope.getTmpVar("_entity")
+        scope.builder().apply {
+            scope.builder().addStatement("final $T $L", entity.typeName, entityVar)
+            val fieldsWithIndices = entity.fields.map {
+                val indexVar = scope.getTmpVar(
+                        "_cursorIndexOf${it.name.stripNonJava().capitalize()}")
+                scope.builder().addStatement("final $T $L = $N.getColumnIndex($S)",
+                        TypeName.INT, indexVar, cursorParam, it.columnName)
+                FieldWithIndex(field = it,
+                        indexVar = indexVar,
+                        alwaysExists = false)
+            }
+            FieldReadWriteWriter.readFromCursor(
+                    outVar = entityVar,
+                    outPojo = entity,
+                    cursorVar = cursorParam.name,
+                    fieldsWithIndices = fieldsWithIndices,
+                    scope = scope)
+            addStatement("return $L", entityVar)
+        }
+        return scope.builder().build()
+    }
+}
diff --git a/room/compiler/src/main/kotlin/android/arch/persistence/room/writer/EntityDeletionAdapterWriter.kt b/room/compiler/src/main/kotlin/android/arch/persistence/room/writer/EntityDeletionAdapterWriter.kt
new file mode 100644
index 0000000..29ba984
--- /dev/null
+++ b/room/compiler/src/main/kotlin/android/arch/persistence/room/writer/EntityDeletionAdapterWriter.kt
@@ -0,0 +1,70 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.arch.persistence.room.writer
+
+import android.arch.persistence.room.ext.L
+import android.arch.persistence.room.ext.RoomTypeNames
+import android.arch.persistence.room.ext.S
+import android.arch.persistence.room.ext.SupportDbTypeNames
+import android.arch.persistence.room.solver.CodeGenScope
+import android.arch.persistence.room.vo.Entity
+import android.arch.persistence.room.vo.FieldWithIndex
+import com.squareup.javapoet.ClassName
+import com.squareup.javapoet.MethodSpec
+import com.squareup.javapoet.ParameterSpec
+import com.squareup.javapoet.ParameterizedTypeName
+import com.squareup.javapoet.TypeName
+import com.squareup.javapoet.TypeSpec
+import javax.lang.model.element.Modifier.PUBLIC
+
+class EntityDeletionAdapterWriter(val entity: Entity) {
+    fun createAnonymous(classWriter: ClassWriter, dbParam: String): TypeSpec {
+        @Suppress("RemoveSingleExpressionStringTemplate")
+        return TypeSpec.anonymousClassBuilder("$L", dbParam).apply {
+            superclass(ParameterizedTypeName.get(RoomTypeNames.DELETE_OR_UPDATE_ADAPTER,
+                    entity.typeName)
+            )
+            addMethod(MethodSpec.methodBuilder("createQuery").apply {
+                addAnnotation(Override::class.java)
+                returns(ClassName.get("java.lang", "String"))
+                addModifiers(PUBLIC)
+                val query = "DELETE FROM `${entity.tableName}` WHERE " +
+                        entity.primaryKey.fields.joinToString(" AND ") {
+                            "`${it.columnName}` = ?"
+                        }
+                addStatement("return $S", query)
+            }.build())
+            addMethod(MethodSpec.methodBuilder("bind").apply {
+                val bindScope = CodeGenScope(classWriter)
+                addAnnotation(Override::class.java)
+                val stmtParam = "stmt"
+                addParameter(ParameterSpec.builder(SupportDbTypeNames.SQLITE_STMT,
+                        stmtParam).build())
+                val valueParam = "value"
+                addParameter(ParameterSpec.builder(entity.typeName, valueParam).build())
+                returns(TypeName.VOID)
+                addModifiers(PUBLIC)
+                val mapped = FieldWithIndex.byOrder(entity.primaryKey.fields)
+                FieldReadWriteWriter.bindToStatement(ownerVar = valueParam,
+                        stmtParamVar = stmtParam,
+                        fieldsWithIndices = mapped,
+                        scope = bindScope)
+                addCode(bindScope.builder().build())
+            }.build())
+        }.build()
+    }
+}
diff --git a/room/compiler/src/main/kotlin/android/arch/persistence/room/writer/EntityInsertionAdapterWriter.kt b/room/compiler/src/main/kotlin/android/arch/persistence/room/writer/EntityInsertionAdapterWriter.kt
new file mode 100644
index 0000000..cd3e771
--- /dev/null
+++ b/room/compiler/src/main/kotlin/android/arch/persistence/room/writer/EntityInsertionAdapterWriter.kt
@@ -0,0 +1,96 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.arch.persistence.room.writer
+
+import android.arch.persistence.room.ext.L
+import android.arch.persistence.room.ext.RoomTypeNames
+import android.arch.persistence.room.ext.S
+import android.arch.persistence.room.ext.SupportDbTypeNames
+import android.arch.persistence.room.solver.CodeGenScope
+import android.arch.persistence.room.vo.Entity
+import android.arch.persistence.room.vo.FieldWithIndex
+import com.squareup.javapoet.ClassName
+import com.squareup.javapoet.MethodSpec
+import com.squareup.javapoet.ParameterSpec
+import com.squareup.javapoet.ParameterizedTypeName
+import com.squareup.javapoet.TypeName
+import com.squareup.javapoet.TypeSpec
+import javax.lang.model.element.Modifier.PUBLIC
+
+class EntityInsertionAdapterWriter(val entity: Entity, val onConflict: String) {
+    fun createAnonymous(classWriter: ClassWriter, dbParam : String): TypeSpec {
+        @Suppress("RemoveSingleExpressionStringTemplate")
+        return TypeSpec.anonymousClassBuilder("$L", dbParam).apply {
+            superclass(
+                    ParameterizedTypeName.get(RoomTypeNames.INSERTION_ADAPTER, entity.typeName)
+            )
+
+            // If there is an auto-increment primary key with primitive type, we consider 0 as
+            // not set. For such fields, we must generate a slightly different insertion SQL.
+            val primitiveAutoGenerateField = if (entity.primaryKey.autoGenerateId) {
+                entity.primaryKey.fields.firstOrNull()?.let { field ->
+                    field.statementBinder?.typeMirror()?.let { binderType ->
+                        if (binderType.kind.isPrimitive) {
+                            field
+                        } else {
+                            null
+                        }
+                    }
+                }
+            } else {
+                null
+            }
+            addMethod(MethodSpec.methodBuilder("createQuery").apply {
+                addAnnotation(Override::class.java)
+                returns(ClassName.get("java.lang", "String"))
+                addModifiers(PUBLIC)
+                val query =
+                        "INSERT OR $onConflict INTO `${entity.tableName}`(" +
+                                entity.fields.joinToString(",") {
+                                    "`${it.columnName}`"
+                                } + ") VALUES (" +
+                                entity.fields.joinToString(",") {
+                                    if (primitiveAutoGenerateField == it) {
+                                        "nullif(?, 0)"
+                                    } else {
+                                        "?"
+                                    }
+                                } + ")"
+                addStatement("return $S", query)
+            }.build())
+            addMethod(MethodSpec.methodBuilder("bind").apply {
+                val bindScope = CodeGenScope(classWriter)
+                addAnnotation(Override::class.java)
+                val stmtParam = "stmt"
+                addParameter(ParameterSpec.builder(SupportDbTypeNames.SQLITE_STMT,
+                        stmtParam).build())
+                val valueParam = "value"
+                addParameter(ParameterSpec.builder(entity.typeName, valueParam).build())
+                returns(TypeName.VOID)
+                addModifiers(PUBLIC)
+                val mapped = FieldWithIndex.byOrder(entity.fields)
+                FieldReadWriteWriter.bindToStatement(
+                        ownerVar = valueParam,
+                        stmtParamVar = stmtParam,
+                        fieldsWithIndices = mapped,
+                        scope = bindScope
+                )
+                addCode(bindScope.builder().build())
+            }.build())
+        }.build()
+    }
+}
diff --git a/room/compiler/src/main/kotlin/android/arch/persistence/room/writer/EntityUpdateAdapterWriter.kt b/room/compiler/src/main/kotlin/android/arch/persistence/room/writer/EntityUpdateAdapterWriter.kt
new file mode 100644
index 0000000..b5711dc
--- /dev/null
+++ b/room/compiler/src/main/kotlin/android/arch/persistence/room/writer/EntityUpdateAdapterWriter.kt
@@ -0,0 +1,86 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.arch.persistence.room.writer
+
+import android.arch.persistence.room.ext.L
+import android.arch.persistence.room.ext.RoomTypeNames
+import android.arch.persistence.room.ext.S
+import android.arch.persistence.room.ext.SupportDbTypeNames
+import android.arch.persistence.room.solver.CodeGenScope
+import android.arch.persistence.room.vo.Entity
+import android.arch.persistence.room.vo.FieldWithIndex
+import com.squareup.javapoet.ClassName
+import com.squareup.javapoet.MethodSpec
+import com.squareup.javapoet.ParameterSpec
+import com.squareup.javapoet.ParameterizedTypeName
+import com.squareup.javapoet.TypeName
+import com.squareup.javapoet.TypeSpec
+import javax.lang.model.element.Modifier.PUBLIC
+
+class EntityUpdateAdapterWriter(val entity: Entity, val onConflict : String) {
+    fun createAnonymous(classWriter: ClassWriter, dbParam: String): TypeSpec {
+        @Suppress("RemoveSingleExpressionStringTemplate")
+        return TypeSpec.anonymousClassBuilder("$L", dbParam).apply {
+            superclass(ParameterizedTypeName.get(RoomTypeNames.DELETE_OR_UPDATE_ADAPTER,
+                    entity.typeName)
+            )
+            addMethod(MethodSpec.methodBuilder("createQuery").apply {
+                addAnnotation(Override::class.java)
+                returns(ClassName.get("java.lang", "String"))
+                addModifiers(PUBLIC)
+                val query = "UPDATE OR $onConflict `${entity.tableName}` SET " +
+                        entity.fields.joinToString(",") { field ->
+                            "`${field.columnName}` = ?"
+                        } + " WHERE " + entity.primaryKey.fields.joinToString(" AND ") {
+                            "`${it.columnName}` = ?"
+                        }
+                addStatement("return $S", query)
+            }.build())
+            addMethod(MethodSpec.methodBuilder("bind").apply {
+                val bindScope = CodeGenScope(classWriter)
+                addAnnotation(Override::class.java)
+                val stmtParam = "stmt"
+                addParameter(ParameterSpec.builder(SupportDbTypeNames.SQLITE_STMT,
+                        stmtParam).build())
+                val valueParam = "value"
+                addParameter(ParameterSpec.builder(entity.typeName, valueParam).build())
+                returns(TypeName.VOID)
+                addModifiers(PUBLIC)
+                val mappedField = FieldWithIndex.byOrder(entity.fields)
+                FieldReadWriteWriter.bindToStatement(
+                        ownerVar = valueParam,
+                        stmtParamVar = stmtParam,
+                        fieldsWithIndices = mappedField,
+                        scope = bindScope
+                )
+                val pkeyStart = entity.fields.size
+                val mappedPrimaryKeys = entity.primaryKey.fields.mapIndexed { index, field ->
+                    FieldWithIndex(field = field,
+                            indexVar = "${pkeyStart + index + 1}",
+                            alwaysExists = true)
+                }
+                FieldReadWriteWriter.bindToStatement(
+                        ownerVar = valueParam,
+                        stmtParamVar = stmtParam,
+                        fieldsWithIndices = mappedPrimaryKeys,
+                        scope = bindScope
+                )
+                addCode(bindScope.builder().build())
+            }.build())
+        }.build()
+    }
+}
diff --git a/room/compiler/src/main/kotlin/android/arch/persistence/room/writer/FieldReadWriteWriter.kt b/room/compiler/src/main/kotlin/android/arch/persistence/room/writer/FieldReadWriteWriter.kt
new file mode 100644
index 0000000..efe57e7
--- /dev/null
+++ b/room/compiler/src/main/kotlin/android/arch/persistence/room/writer/FieldReadWriteWriter.kt
@@ -0,0 +1,358 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.arch.persistence.room.writer
+
+import android.arch.persistence.room.ext.L
+import android.arch.persistence.room.ext.T
+import android.arch.persistence.room.ext.defaultValue
+import android.arch.persistence.room.ext.typeName
+import android.arch.persistence.room.solver.CodeGenScope
+import android.arch.persistence.room.vo.CallType
+import android.arch.persistence.room.vo.Constructor
+import android.arch.persistence.room.vo.Field
+import android.arch.persistence.room.vo.EmbeddedField
+import android.arch.persistence.room.vo.FieldWithIndex
+import android.arch.persistence.room.vo.Pojo
+import com.squareup.javapoet.TypeName
+
+/**
+ * Handles writing a field into statement or reading it form statement.
+ */
+class FieldReadWriteWriter(fieldWithIndex: FieldWithIndex) {
+    val field = fieldWithIndex.field
+    val indexVar = fieldWithIndex.indexVar
+    val alwaysExists = fieldWithIndex.alwaysExists
+
+    companion object {
+        /*
+         * Get all parents including the ones which have grand children in this list but does not
+         * have any direct children in the list.
+         */
+        fun getAllParents(fields: List<Field>): Set<EmbeddedField> {
+            val allParents = mutableSetOf<EmbeddedField>()
+            fun addAllParents(field: Field) {
+                var parent = field.parent
+                while (parent != null) {
+                    if (allParents.add(parent)) {
+                        parent = parent.parent
+                    } else {
+                        break
+                    }
+                }
+            }
+            fields.forEach(::addAllParents)
+            return allParents
+        }
+
+        /**
+         * Convert the fields with indices into a Node tree so that we can recursively process
+         * them. This work is done here instead of parsing because the result may include arbitrary
+         * fields.
+         */
+        private fun createNodeTree(rootVar: String,
+                           fieldsWithIndices: List<FieldWithIndex>,
+                           scope: CodeGenScope): Node {
+            val allParents = getAllParents(fieldsWithIndices.map { it.field })
+            val rootNode = Node(rootVar, null)
+            rootNode.directFields = fieldsWithIndices.filter { it.field.parent == null }
+            val parentNodes = allParents.associate {
+                Pair(it, Node(
+                        varName = scope.getTmpVar("_tmp${it.field.name.capitalize()}"),
+                        fieldParent = it))
+            }
+            parentNodes.values.forEach { node ->
+                val fieldParent = node.fieldParent!!
+                val grandParent = fieldParent.parent
+                val grandParentNode = grandParent?.let {
+                    parentNodes[it]
+                } ?: rootNode
+                node.directFields = fieldsWithIndices.filter { it.field.parent == fieldParent }
+                node.parentNode = grandParentNode
+                grandParentNode.subNodes.add(node)
+            }
+            return rootNode
+        }
+
+        fun bindToStatement(ownerVar: String, stmtParamVar: String,
+                            fieldsWithIndices: List<FieldWithIndex>,
+                            scope: CodeGenScope) {
+            fun visitNode(node: Node) {
+                fun bindWithDescendants() {
+                    node.directFields.forEach {
+                        FieldReadWriteWriter(it).bindToStatement(
+                                ownerVar = node.varName,
+                                stmtParamVar = stmtParamVar,
+                                scope = scope
+                        )
+                    }
+                    node.subNodes.forEach(::visitNode)
+                }
+
+                val fieldParent = node.fieldParent
+                if (fieldParent != null) {
+                    fieldParent.getter.writeGet(
+                            ownerVar = node.parentNode!!.varName,
+                            outVar = node.varName,
+                            builder = scope.builder()
+                    )
+                    scope.builder().apply {
+                        beginControlFlow("if($L != null)", node.varName).apply {
+                            bindWithDescendants()
+                        }
+                        nextControlFlow("else").apply {
+                            node.allFields().forEach {
+                                addStatement("$L.bindNull($L)", stmtParamVar, it.indexVar)
+                            }
+                        }
+                        endControlFlow()
+                    }
+                } else {
+                    bindWithDescendants()
+                }
+            }
+            visitNode(createNodeTree(ownerVar, fieldsWithIndices, scope))
+        }
+
+        /**
+         * Just constructs the given item, does NOT DECLARE. Declaration happens outside the
+         * reading statement since we may never read if the cursor does not have necessary
+         * columns.
+         */
+        private fun construct(outVar : String, constructor : Constructor?, typeName : TypeName,
+                              localVariableNames : Map<String, FieldWithIndex>,
+                              localEmbeddeds: List<Node>, scope: CodeGenScope) {
+            if (constructor == null) {
+                // best hope code generation
+                scope.builder().apply {
+                    addStatement("$L = new $T()", outVar, typeName)
+                }
+                return
+            }
+            val variableNames = constructor.params.map { param ->
+                when(param) {
+                    is Constructor.FieldParam -> localVariableNames.entries.firstOrNull {
+                        it.value.field === param.field
+                    }?.key
+                    is Constructor.EmbeddedParam -> localEmbeddeds.firstOrNull {
+                        it.fieldParent === param.embedded
+                    }?.varName
+                    else -> null
+                }
+            }
+            val args = variableNames.joinToString(",") { it ?: "null"}
+            scope.builder().apply {
+                addStatement("$L = new $T($L)", outVar, typeName, args)
+            }
+        }
+
+        /**
+         * Reads the row into the given variable. It does not declare it but constructs it.
+         */
+        fun readFromCursor(outVar: String,
+                           outPojo : Pojo,
+                           cursorVar: String,
+                           fieldsWithIndices: List<FieldWithIndex>,
+                           scope: CodeGenScope) {
+            fun visitNode(node: Node) {
+                val fieldParent = node.fieldParent
+                fun readNode() {
+                    // read constructor parameters into local fields
+                    val constructorFields = node.directFields.filter {
+                        it.field.setter.callType == CallType.CONSTRUCTOR
+                    }.associateBy { fwi ->
+                        FieldReadWriteWriter(fwi).readIntoTmpVar(cursorVar, scope)
+                    }
+                    // read decomposed fields
+                    node.subNodes.forEach(::visitNode)
+                    // construct the object
+                    if (fieldParent != null) {
+                        construct(outVar = node.varName,
+                                constructor = fieldParent.pojo.constructor,
+                                typeName = fieldParent.field.typeName,
+                                localEmbeddeds = node.subNodes,
+                                localVariableNames = constructorFields,
+                                scope = scope)
+                    } else {
+                        construct(outVar = node.varName,
+                                constructor = outPojo.constructor,
+                                typeName = outPojo.typeName,
+                                localEmbeddeds = node.subNodes,
+                                localVariableNames = constructorFields,
+                                scope = scope)
+                    }
+                    // ready any field that was not part of the constructor
+                    node.directFields.filterNot {
+                        it.field.setter.callType == CallType.CONSTRUCTOR
+                    }.forEach { fwi ->
+                        FieldReadWriteWriter(fwi).readFromCursor(
+                                ownerVar = node.varName,
+                                cursorVar = cursorVar,
+                                scope = scope)
+                    }
+                    // assign sub modes to fields if they were not part of the constructor.
+                    node.subNodes.map {
+                        val setter = it.fieldParent?.setter
+                        if (setter != null && setter.callType != CallType.CONSTRUCTOR) {
+                            Pair(it.varName, setter)
+                        } else {
+                            null
+                        }
+                    }.filterNotNull().forEach { pair ->
+                        val varName = pair.first
+                        val setter = pair.second
+                        setter.writeSet(
+                                ownerVar = node.varName,
+                                inVar = varName,
+                                builder = scope.builder())
+                    }
+                }
+                if (fieldParent == null) {
+                    // root element
+                    // always declared by the caller so we don't declare this
+                    readNode()
+                } else {
+                    // always declare, we'll set below
+                    scope.builder().addStatement("final $T $L", fieldParent.pojo.typeName,
+                                        node.varName)
+                    if (fieldParent.nonNull) {
+                        readNode()
+                    } else {
+                        val myDescendants = node.allFields()
+                        val allNullCheck = myDescendants.joinToString(" && ") {
+                            if (it.alwaysExists) {
+                                "$cursorVar.isNull(${it.indexVar})"
+                            } else {
+                                "( ${it.indexVar} == -1 || $cursorVar.isNull(${it.indexVar}))"
+                            }
+
+                        }
+                        scope.builder().apply {
+                            beginControlFlow("if (! ($L))", allNullCheck).apply {
+                                readNode()
+                            }
+                            nextControlFlow(" else ").apply {
+                                addStatement("$L = null", node.varName)
+                            }
+                            endControlFlow()
+                        }
+                    }
+                }
+            }
+            visitNode(createNodeTree(outVar, fieldsWithIndices, scope))
+        }
+    }
+
+    /**
+     * @param ownerVar The entity / pojo that owns this field. It must own this field! (not the
+     * container pojo)
+     * @param stmtParamVar The statement variable
+     * @param scope The code generation scope
+     */
+    private fun bindToStatement(ownerVar: String, stmtParamVar: String, scope: CodeGenScope) {
+        field.statementBinder?.let { binder ->
+            val varName = if (field.getter.callType == CallType.FIELD) {
+                "$ownerVar.${field.name}"
+            } else {
+                "$ownerVar.${field.getter.name}()"
+            }
+            binder.bindToStmt(stmtParamVar, indexVar, varName, scope)
+        }
+    }
+
+    /**
+     * @param ownerVar The entity / pojo that owns this field. It must own this field (not the
+     * container pojo)
+     * @param cursorVar The cursor variable
+     * @param scope The code generation scope
+     */
+    private fun readFromCursor(ownerVar: String, cursorVar: String, scope: CodeGenScope) {
+        fun toRead() {
+            field.cursorValueReader?.let { reader ->
+                scope.builder().apply {
+                    when (field.setter.callType) {
+                        CallType.FIELD -> {
+                            reader.readFromCursor("$ownerVar.${field.getter.name}", cursorVar,
+                                    indexVar, scope)
+                        }
+                        CallType.METHOD -> {
+                            val tmpField = scope.getTmpVar("_tmp${field.name.capitalize()}")
+                            addStatement("final $T $L", field.getter.type.typeName(), tmpField)
+                            reader.readFromCursor(tmpField, cursorVar, indexVar, scope)
+                            addStatement("$L.$L($L)", ownerVar, field.setter.name, tmpField)
+                        }
+                        CallType.CONSTRUCTOR -> {
+                            // no-op
+                        }
+                    }
+                }
+            }
+        }
+        if (alwaysExists) {
+            toRead()
+        } else {
+            scope.builder().apply {
+                beginControlFlow("if ($L != -1)", indexVar).apply {
+                    toRead()
+                }
+                endControlFlow()
+            }
+        }
+    }
+
+    /**
+     * Reads the value into a temporary local variable.
+     */
+    fun readIntoTmpVar(cursorVar: String, scope: CodeGenScope) : String {
+        val tmpField = scope.getTmpVar("_tmp${field.name.capitalize()}")
+        val typeName = field.getter.type.typeName()
+        scope.builder().apply {
+            addStatement("final $T $L", typeName, tmpField)
+            if (alwaysExists) {
+                field.cursorValueReader?.readFromCursor(tmpField, cursorVar, indexVar, scope)
+            } else {
+                beginControlFlow("if ($L == -1)", indexVar).apply {
+                    addStatement("$L = $L", tmpField, typeName.defaultValue())
+                }
+                nextControlFlow("else").apply {
+                    field.cursorValueReader?.readFromCursor(tmpField, cursorVar, indexVar, scope)
+                }
+                endControlFlow()
+            }
+        }
+        return tmpField
+    }
+
+    /**
+     * On demand node which is created based on the fields that were passed into this class.
+     */
+    private class Node(
+            // root for me
+            val varName: String,
+            // set if I'm a FieldParent
+            val fieldParent: EmbeddedField?) {
+        // whom do i belong
+        var parentNode: Node? = null
+        // these fields are my direct fields
+        lateinit var directFields: List<FieldWithIndex>
+        // these nodes are under me
+        val subNodes = mutableListOf<Node>()
+
+        fun allFields(): List<FieldWithIndex> {
+            return directFields + subNodes.flatMap { it.allFields() }
+        }
+    }
+}
diff --git a/room/compiler/src/main/kotlin/android/arch/persistence/room/writer/PreparedStatementWriter.kt b/room/compiler/src/main/kotlin/android/arch/persistence/room/writer/PreparedStatementWriter.kt
new file mode 100644
index 0000000..9c3e612
--- /dev/null
+++ b/room/compiler/src/main/kotlin/android/arch/persistence/room/writer/PreparedStatementWriter.kt
@@ -0,0 +1,49 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package android.arch.persistence.room.writer
+
+import android.arch.persistence.room.ext.L
+import android.arch.persistence.room.ext.N
+import android.arch.persistence.room.ext.RoomTypeNames
+import android.arch.persistence.room.solver.CodeGenScope
+import com.squareup.javapoet.ClassName
+import com.squareup.javapoet.FieldSpec
+import com.squareup.javapoet.MethodSpec
+import com.squareup.javapoet.TypeSpec
+import javax.lang.model.element.Modifier
+
+/**
+ * Creates anonymous classes for RoomTypeNames#SHARED_SQLITE_STMT.
+ */
+class PreparedStatementWriter(val queryWriter: QueryWriter) {
+    fun createAnonymous(classWriter: ClassWriter, dbParam : FieldSpec): TypeSpec {
+        val scope = CodeGenScope(classWriter)
+        @Suppress("RemoveSingleExpressionStringTemplate")
+        return TypeSpec.anonymousClassBuilder("$N", dbParam).apply {
+            superclass(RoomTypeNames.SHARED_SQLITE_STMT)
+            addMethod(MethodSpec.methodBuilder("createQuery").apply {
+                addAnnotation(Override::class.java)
+                returns(ClassName.get("java.lang", "String"))
+                addModifiers(Modifier.PUBLIC)
+                val queryName = scope.getTmpVar("_query")
+                val queryGenScope = scope.fork()
+                queryWriter.prepareQuery(queryName, queryGenScope)
+                addCode(queryGenScope.builder().build())
+                addStatement("return $L", queryName)
+            }.build())
+        }.build()
+    }
+}
diff --git a/room/compiler/src/main/kotlin/android/arch/persistence/room/writer/QueryWriter.kt b/room/compiler/src/main/kotlin/android/arch/persistence/room/writer/QueryWriter.kt
new file mode 100644
index 0000000..9e72bef
--- /dev/null
+++ b/room/compiler/src/main/kotlin/android/arch/persistence/room/writer/QueryWriter.kt
@@ -0,0 +1,153 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.arch.persistence.room.writer
+
+import android.arch.persistence.room.ext.L
+import android.arch.persistence.room.ext.RoomTypeNames.ROOM_SQL_QUERY
+import android.arch.persistence.room.ext.RoomTypeNames.STRING_UTIL
+import android.arch.persistence.room.ext.S
+import android.arch.persistence.room.ext.T
+import android.arch.persistence.room.ext.typeName
+import android.arch.persistence.room.parser.ParsedQuery
+import android.arch.persistence.room.parser.Section
+import android.arch.persistence.room.parser.SectionType.BIND_VAR
+import android.arch.persistence.room.parser.SectionType.NEWLINE
+import android.arch.persistence.room.parser.SectionType.TEXT
+import android.arch.persistence.room.solver.CodeGenScope
+import android.arch.persistence.room.vo.QueryMethod
+import android.arch.persistence.room.vo.QueryParameter
+import com.squareup.javapoet.ClassName
+import com.squareup.javapoet.TypeName
+
+/**
+ * Writes the SQL query and arguments for a QueryMethod.
+ */
+class QueryWriter constructor(val parameters : List<QueryParameter>,
+                              val sectionToParamMapping : List<Pair<Section, QueryParameter?>>,
+                              val query : ParsedQuery) {
+
+    constructor(queryMethod: QueryMethod) : this(queryMethod.parameters,
+            queryMethod.sectionToParamMapping, queryMethod.query) {
+    }
+
+    fun prepareReadAndBind(outSqlQueryName: String, outRoomSQLiteQueryVar: String,
+                           scope: CodeGenScope) {
+        val listSizeVars = createSqlQueryAndArgs(outSqlQueryName, outRoomSQLiteQueryVar, scope)
+        bindArgs(outRoomSQLiteQueryVar, listSizeVars, scope)
+    }
+
+    fun prepareQuery(outSqlQueryName: String, scope: CodeGenScope) {
+        createSqlQueryAndArgs(outSqlQueryName, null, scope)
+    }
+
+    private fun createSqlQueryAndArgs(outSqlQueryName: String, outArgsName: String?,
+                                      scope: CodeGenScope): List<Pair<QueryParameter, String>> {
+        val listSizeVars = arrayListOf<Pair<QueryParameter, String>>()
+        val varargParams = parameters
+                .filter { it.queryParamAdapter?.isMultiple ?: false }
+        val sectionToParamMapping = sectionToParamMapping
+        val knownQueryArgsCount = sectionToParamMapping.filterNot {
+            it.second?.queryParamAdapter?.isMultiple ?: false
+        }.size
+        scope.builder().apply {
+            if (varargParams.isNotEmpty()) {
+                val stringBuilderVar = scope.getTmpVar("_stringBuilder")
+                addStatement("$T $L = $T.newStringBuilder()",
+                        ClassName.get(StringBuilder::class.java), stringBuilderVar, STRING_UTIL)
+                query.sections.forEach {
+                    when (it.type) {
+                        TEXT -> addStatement("$L.append($S)", stringBuilderVar, it.text)
+                        NEWLINE -> addStatement("$L.append($S)", "\n")
+                        BIND_VAR -> {
+                            // If it is null, will be reported as error before. We just try out
+                            // best to generate as much code as possible.
+                            sectionToParamMapping.firstOrNull { mapping ->
+                                mapping.first == it
+                            }?.let { pair ->
+                                if (pair.second?.queryParamAdapter?.isMultiple ?: false) {
+                                    val tmpCount = scope.getTmpVar("_inputSize")
+                                    listSizeVars.add(Pair(pair.second!!, tmpCount))
+                                    pair.second
+                                            ?.queryParamAdapter
+                                            ?.getArgCount(pair.second!!.name, tmpCount, scope)
+                                    addStatement("$T.appendPlaceholders($L, $L)",
+                                            STRING_UTIL, stringBuilderVar, tmpCount)
+                                } else {
+                                    addStatement("$L.append($S)", stringBuilderVar, "?")
+                                }
+                            }
+                        }
+                    }
+                }
+
+                addStatement("final $T $L = $L.toString()", String::class.typeName(),
+                        outSqlQueryName, stringBuilderVar)
+                if (outArgsName != null) {
+                    val argCount = scope.getTmpVar("_argCount")
+                    addStatement("final $T $L = $L$L", TypeName.INT, argCount, knownQueryArgsCount,
+                            listSizeVars.joinToString("") { " + ${it.second}" })
+                    addStatement("final $T $L = $T.acquire($L, $L)",
+                            ROOM_SQL_QUERY, outArgsName, ROOM_SQL_QUERY, outSqlQueryName,
+                            argCount)
+                }
+            } else {
+                addStatement("final $T $L = $S", String::class.typeName(),
+                        outSqlQueryName, query.queryWithReplacedBindParams)
+                if (outArgsName != null) {
+                    addStatement("final $T $L = $T.acquire($L, $L)",
+                            ROOM_SQL_QUERY, outArgsName, ROOM_SQL_QUERY, outSqlQueryName,
+                            knownQueryArgsCount)
+                }
+            }
+        }
+        return listSizeVars
+    }
+
+    fun bindArgs(outArgsName: String, listSizeVars : List<Pair<QueryParameter, String>>,
+                         scope: CodeGenScope) {
+        if (parameters.isEmpty()) {
+            return
+        }
+        scope.builder().apply {
+            val argIndex = scope.getTmpVar("_argIndex")
+            addStatement("$T $L = $L", TypeName.INT, argIndex, 1)
+            // # of bindings with 1 placeholder
+            var constInputs = 0
+            // variable names for size of the bindings that have multiple  args
+            val varInputs = arrayListOf<String>()
+            sectionToParamMapping.forEach { pair ->
+                // reset the argIndex to the correct start index
+                if (constInputs > 0 || varInputs.isNotEmpty()) {
+                    addStatement("$L = $L$L", argIndex,
+                            if (constInputs > 0) (1 + constInputs) else "1",
+                            varInputs.joinToString("") { " + $it" })
+                }
+                val param = pair.second
+                param?.let {
+                    param.queryParamAdapter?.bindToStmt(param.name, outArgsName, argIndex, scope)
+                }
+                // add these to the list so that we can use them to calculate the next count.
+                val sizeVar = listSizeVars.firstOrNull { it.first == param }
+                if (sizeVar == null) {
+                    constInputs ++
+                } else {
+                    varInputs.add(sizeVar.second)
+                }
+            }
+        }
+    }
+}
diff --git a/room/compiler/src/main/kotlin/android/arch/persistence/room/writer/RelationCollectorMethodWriter.kt b/room/compiler/src/main/kotlin/android/arch/persistence/room/writer/RelationCollectorMethodWriter.kt
new file mode 100644
index 0000000..092cbb0
--- /dev/null
+++ b/room/compiler/src/main/kotlin/android/arch/persistence/room/writer/RelationCollectorMethodWriter.kt
@@ -0,0 +1,126 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.arch.persistence.room.writer
+
+import android.arch.persistence.room.ext.AndroidTypeNames
+import android.arch.persistence.room.ext.L
+import android.arch.persistence.room.ext.N
+import android.arch.persistence.room.ext.S
+import android.arch.persistence.room.ext.T
+import android.arch.persistence.room.solver.CodeGenScope
+import android.arch.persistence.room.vo.RelationCollector
+import com.squareup.javapoet.ClassName
+import com.squareup.javapoet.MethodSpec
+import com.squareup.javapoet.ParameterSpec
+import com.squareup.javapoet.ParameterizedTypeName
+import com.squareup.javapoet.TypeName
+import stripNonJava
+import javax.lang.model.element.Modifier
+
+/**
+ * Writes the method that fetches the relations of a POJO and assigns them into the given map.
+ */
+class RelationCollectorMethodWriter(val collector: RelationCollector)
+    : ClassWriter.SharedMethodSpec(
+        "fetchRelationship${collector.relation.entity.tableName.stripNonJava()}" +
+                "As${collector.relation.pojo.typeName.toString().stripNonJava()}") {
+    companion object {
+        val KEY_SET_VARIABLE = "__mapKeySet"
+    }
+    override fun getUniqueKey(): String {
+        val relation = collector.relation
+        return "RelationCollectorMethodWriter" +
+                "-${collector.mapTypeName}" +
+                "-${relation.entity.typeName}" +
+                "-${relation.entityField.columnName}" +
+                "-${relation.pojo.typeName}" +
+                "-${relation.createLoadAllSql()}"
+    }
+
+    override fun prepare(writer: ClassWriter, builder: MethodSpec.Builder) {
+        val scope = CodeGenScope(writer)
+        val relation = collector.relation
+
+        val param = ParameterSpec.builder(collector.mapTypeName, "_map")
+                .addModifiers(Modifier.FINAL)
+                .build()
+        val sqlQueryVar = scope.getTmpVar("_sql")
+        val keySetVar = KEY_SET_VARIABLE
+
+        val cursorVar = "_cursor"
+        val itemKeyIndexVar = "_itemKeyIndex"
+        val stmtVar = scope.getTmpVar("_stmt")
+        scope.builder().apply {
+
+            val keySetType = ParameterizedTypeName.get(
+                    ClassName.get(Set::class.java), collector.keyTypeName
+            )
+            addStatement("final $T $L = $N.keySet()", keySetType, keySetVar, param)
+            beginControlFlow("if ($L.isEmpty())", keySetVar).apply {
+                addStatement("return")
+            }
+            endControlFlow()
+            collector.queryWriter.prepareReadAndBind(sqlQueryVar, stmtVar, scope)
+
+            addStatement("final $T $L = $N.query($L)", AndroidTypeNames.CURSOR, cursorVar,
+                    DaoWriter.dbField, stmtVar)
+
+            beginControlFlow("try").apply {
+                addStatement("final $T $L = $L.getColumnIndex($S)",
+                        TypeName.INT, itemKeyIndexVar, cursorVar, relation.entityField.columnName)
+
+                beginControlFlow("if ($L == -1)", itemKeyIndexVar).apply {
+                    addStatement("return")
+                }
+                endControlFlow()
+
+                collector.rowAdapter.onCursorReady(cursorVar, scope)
+                val tmpVarName = scope.getTmpVar("_item")
+                beginControlFlow("while($L.moveToNext())", cursorVar).apply {
+                    // read key from the cursor
+                    collector.readKey(
+                            cursorVarName = cursorVar,
+                            indexVar = itemKeyIndexVar,
+                            scope = scope
+                    ) { keyVar ->
+                        val collectionVar = scope.getTmpVar("_tmpCollection")
+                        addStatement("$T $L = $N.get($L)", collector.collectionTypeName,
+                                collectionVar, param, keyVar)
+                        beginControlFlow("if ($L != null)", collectionVar).apply {
+                            addStatement("final $T $L", relation.pojo.typeName, tmpVarName)
+                            collector.rowAdapter.convert(tmpVarName, cursorVar, scope)
+                            addStatement("$L.add($L)", collectionVar, tmpVarName)
+                        }
+                        endControlFlow()
+                    }
+                }
+                endControlFlow()
+                collector.rowAdapter.onCursorFinished()?.invoke(scope)
+            }
+            nextControlFlow("finally").apply {
+                addStatement("$L.close()", cursorVar)
+            }
+            endControlFlow()
+        }
+        builder.apply {
+            addModifiers(Modifier.PRIVATE)
+            addParameter(param)
+            returns(TypeName.VOID)
+            addCode(scope.builder().build())
+        }
+    }
+}
diff --git a/room/compiler/src/main/kotlin/android/arch/persistence/room/writer/SQLiteOpenHelperWriter.kt b/room/compiler/src/main/kotlin/android/arch/persistence/room/writer/SQLiteOpenHelperWriter.kt
new file mode 100644
index 0000000..50dcb32
--- /dev/null
+++ b/room/compiler/src/main/kotlin/android/arch/persistence/room/writer/SQLiteOpenHelperWriter.kt
@@ -0,0 +1,129 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.arch.persistence.room.writer
+
+import android.support.annotation.VisibleForTesting
+import android.arch.persistence.room.ext.L
+import android.arch.persistence.room.ext.N
+import android.arch.persistence.room.ext.RoomTypeNames
+import android.arch.persistence.room.ext.S
+import android.arch.persistence.room.ext.SupportDbTypeNames
+import android.arch.persistence.room.ext.T
+import android.arch.persistence.room.solver.CodeGenScope
+import android.arch.persistence.room.vo.Database
+import android.arch.persistence.room.vo.Entity
+import com.squareup.javapoet.MethodSpec
+import com.squareup.javapoet.ParameterSpec
+import com.squareup.javapoet.TypeSpec
+import javax.lang.model.element.Modifier.PROTECTED
+import javax.lang.model.element.Modifier.PUBLIC
+
+/**
+ * Create an open helper using SupportSQLiteOpenHelperFactory
+ */
+class SQLiteOpenHelperWriter(val database : Database) {
+    fun write(outVar : String, configuration : ParameterSpec, scope: CodeGenScope) {
+        scope.builder().apply {
+            val sqliteConfigVar = scope.getTmpVar("_sqliteConfig")
+            val callbackVar = scope.getTmpVar("_openCallback")
+            addStatement("final $T $L = new $T($N, $L, $S)",
+                    SupportDbTypeNames.SQLITE_OPEN_HELPER_CALLBACK,
+                    callbackVar, RoomTypeNames.OPEN_HELPER, configuration,
+                    createOpenCallback(scope), database.identityHash)
+            // build configuration
+            addStatement(
+                    """
+                    final $T $L = $T.builder($N.context)
+                    .name($N.name)
+                    .version($L)
+                    .callback($L)
+                    .build()
+                    """.trimIndent(),
+                    SupportDbTypeNames.SQLITE_OPEN_HELPER_CONFIG, sqliteConfigVar,
+                    SupportDbTypeNames.SQLITE_OPEN_HELPER_CONFIG,
+                    configuration, configuration, database.version, callbackVar)
+            addStatement("final $T $N = $N.sqliteOpenHelperFactory.create($L)",
+                    SupportDbTypeNames.SQLITE_OPEN_HELPER, outVar,
+                    configuration, sqliteConfigVar)
+        }
+    }
+
+    private fun createOpenCallback(scope: CodeGenScope) : TypeSpec {
+        return TypeSpec.anonymousClassBuilder("").apply {
+            superclass(RoomTypeNames.OPEN_HELPER_DELEGATE)
+            addMethod(createCreateAllTables())
+            addMethod(createDropAllTables())
+            addMethod(createOnOpen())
+            addMethod(createValidateMigration(scope.fork()))
+        }.build()
+    }
+
+    private fun createValidateMigration(scope: CodeGenScope): MethodSpec {
+        return MethodSpec.methodBuilder("validateMigration").apply {
+            addModifiers(PROTECTED)
+            val dbParam = ParameterSpec.builder(SupportDbTypeNames.DB, "_db").build()
+            addParameter(dbParam)
+            database.entities.forEach { entity ->
+                val methodScope = scope.fork()
+                TableInfoValidationWriter(entity).write(dbParam, methodScope)
+                addCode(methodScope.builder().build())
+            }
+        }.build()
+    }
+
+    private fun createOnOpen(): MethodSpec {
+        return MethodSpec.methodBuilder("onOpen").apply {
+            addModifiers(PUBLIC)
+            addParameter(SupportDbTypeNames.DB, "_db")
+            addStatement("mDatabase = _db")
+            if (database.enableForeignKeys) {
+                addStatement("_db.execSQL($S)", "PRAGMA foreign_keys = ON")
+            }
+            addStatement("internalInitInvalidationTracker(_db)")
+        }.build()
+    }
+
+    private fun createCreateAllTables() : MethodSpec {
+        return MethodSpec.methodBuilder("createAllTables").apply {
+            addModifiers(PUBLIC)
+            addParameter(SupportDbTypeNames.DB, "_db")
+            database.bundle.buildCreateQueries().forEach {
+                addStatement("_db.execSQL($S)", it)
+            }
+        }.build()
+    }
+
+    private fun createDropAllTables() : MethodSpec {
+        return MethodSpec.methodBuilder("dropAllTables").apply {
+            addModifiers(PUBLIC)
+            addParameter(SupportDbTypeNames.DB, "_db")
+            database.entities.forEach {
+                addStatement("_db.execSQL($S)", createDropTableQuery(it))
+            }
+        }.build()
+    }
+
+    @VisibleForTesting
+    fun createQuery(entity : Entity) : String {
+        return entity.createTableQuery
+    }
+
+    @VisibleForTesting
+    fun createDropTableQuery(entity: Entity) : String {
+        return "DROP TABLE IF EXISTS `${entity.tableName}`"
+    }
+}
diff --git a/room/compiler/src/main/kotlin/android/arch/persistence/room/writer/TableInfoValidationWriter.kt b/room/compiler/src/main/kotlin/android/arch/persistence/room/writer/TableInfoValidationWriter.kt
new file mode 100644
index 0000000..8da4c12
--- /dev/null
+++ b/room/compiler/src/main/kotlin/android/arch/persistence/room/writer/TableInfoValidationWriter.kt
@@ -0,0 +1,96 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.arch.persistence.room.writer
+
+import android.arch.persistence.room.ext.CommonTypeNames
+import android.arch.persistence.room.ext.L
+import android.arch.persistence.room.ext.N
+import android.arch.persistence.room.ext.RoomTypeNames
+import android.arch.persistence.room.ext.S
+import android.arch.persistence.room.ext.T
+import android.arch.persistence.room.ext.typeName
+import android.arch.persistence.room.parser.SQLTypeAffinity
+import android.arch.persistence.room.solver.CodeGenScope
+import android.arch.persistence.room.vo.Entity
+import com.squareup.javapoet.ParameterSpec
+import com.squareup.javapoet.ParameterizedTypeName
+import stripNonJava
+import java.util.Arrays
+import java.util.HashMap
+import java.util.HashSet
+
+class TableInfoValidationWriter(val entity : Entity) {
+    fun write(dbParam : ParameterSpec, scope : CodeGenScope) {
+        val suffix = entity.tableName.stripNonJava().capitalize()
+        val expectedInfoVar = scope.getTmpVar("_info$suffix")
+        scope.builder().apply {
+            val columnListVar = scope.getTmpVar("_columns$suffix")
+            val columnListType = ParameterizedTypeName.get(HashMap::class.typeName(),
+                    CommonTypeNames.STRING, RoomTypeNames.TABLE_INFO_COLUMN)
+
+            addStatement("final $T $L = new $T($L)", columnListType, columnListVar,
+                    columnListType, entity.fields.size)
+            entity.fields.forEachIndexed { index, field ->
+                addStatement("$L.put($S, new $T($S, $S, $L))",
+                        columnListVar, field.columnName, RoomTypeNames.TABLE_INFO_COLUMN,
+                        /*name*/ field.columnName,
+                        /*type*/ field.affinity?.name ?: SQLTypeAffinity.TEXT.name,
+                        /*pkeyPos*/ entity.primaryKey.fields.indexOf(field) + 1)
+            }
+
+            val foreignKeySetVar = scope.getTmpVar("_foreignKeys$suffix")
+            val foreignKeySetType = ParameterizedTypeName.get(HashSet::class.typeName(),
+                    RoomTypeNames.TABLE_INFO_FOREIGN_KEY)
+            addStatement("final $T $L = new $T($L)", foreignKeySetType, foreignKeySetVar,
+                    foreignKeySetType, entity.foreignKeys.size)
+            entity.foreignKeys.forEach {
+                val myColumnNames = it.childFields
+                        .joinToString(",") { "\"${it.columnName}\"" }
+                val refColumnNames = it.parentColumns
+                        .joinToString(",") { "\"$it\"" }
+                addStatement("$L.add(new $T($S, $S, $S," +
+                        "$T.asList($L), $T.asList($L)))", foreignKeySetVar,
+                        RoomTypeNames.TABLE_INFO_FOREIGN_KEY,
+                        /*parent table*/ it.parentTable,
+                        /*on delete*/ it.onDelete.sqlName,
+                        /*on update*/ it.onUpdate.sqlName,
+                        Arrays::class.typeName(),
+                        /*parent names*/ myColumnNames,
+                        Arrays::class.typeName(),
+                        /*parent column names*/ refColumnNames)
+            }
+
+            addStatement("final $T $L = new $T($S, $L, $L)",
+                    RoomTypeNames.TABLE_INFO, expectedInfoVar, RoomTypeNames.TABLE_INFO,
+                    entity.tableName, columnListVar, foreignKeySetVar)
+
+            val existingVar = scope.getTmpVar("_existing$suffix")
+            addStatement("final $T $L = $T.read($N, $S)",
+                    RoomTypeNames.TABLE_INFO, existingVar, RoomTypeNames.TABLE_INFO,
+                    dbParam, entity.tableName)
+
+            beginControlFlow("if (! $L.equals($L))", expectedInfoVar, existingVar).apply {
+                addStatement("throw new $T($S + $L + $S + $L)",
+                        IllegalStateException::class.typeName(),
+                        "Migration didn't properly handle ${entity.tableName}" +
+                                "(${entity.element.qualifiedName}).\n Expected:\n",
+                        expectedInfoVar, "\n Found:\n", existingVar)
+            }
+            endControlFlow()
+        }
+    }
+}
diff --git a/room/compiler/src/main/resources/META-INF/services/javax.annotation.processing.Processor b/room/compiler/src/main/resources/META-INF/services/javax.annotation.processing.Processor
new file mode 100644
index 0000000..f628f64
--- /dev/null
+++ b/room/compiler/src/main/resources/META-INF/services/javax.annotation.processing.Processor
@@ -0,0 +1 @@
+android.arch.persistence.room.RoomProcessor
diff --git a/room/compiler/src/main/resources/NOTICE.txt b/room/compiler/src/main/resources/NOTICE.txt
new file mode 100644
index 0000000..fd8adbc
--- /dev/null
+++ b/room/compiler/src/main/resources/NOTICE.txt
@@ -0,0 +1,1812 @@
+List of 3rd party licenses:
+-----------------------------------------------------------------------------
+* commons-codec.jar (commons-codec:commons-codec:1.10)
+
+ ****** NOTICE:
+Apache Commons Codec
+Copyright 2002-2016 The Apache Software Foundation
+
+This product includes software developed at
+The Apache Software Foundation (http://www.apache.org/).
+
+src/test/org/apache/commons/codec/language/DoubleMetaphoneTest.java
+contains test data from http://aspell.net/test/orig/batch0.tab.
+Copyright (C) 2002 Kevin Atkinson (kevina@gnu.org)
+
+===============================================================================
+
+The content of package org.apache.commons.codec.language.bm has been translated
+from the original php source code available at http://stevemorse.org/phoneticinfo.htm
+with permission from the original authors.
+Original source copyright:
+Copyright (c) 2008 Alexander Beider & Stephen P. Morse.
+
+ ****** 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
+
+   APPENDIX: How to apply the Apache License to your work.
+
+      To apply the Apache License to your work, attach the following
+      boilerplate notice, with the fields enclosed by brackets "[]"
+      replaced with your own identifying information. (Don't include
+      the brackets!)  The text should be enclosed in the appropriate
+      comment syntax for the file format. We also recommend that a
+      file or class name and description of purpose be included on the
+      same "printed page" as the copyright notice for easier
+      identification within third-party archives.
+
+   Copyright [yyyy] [name of copyright owner]
+
+   Licensed under the Apache License, Version 2.0 (the "License");
+   you may not use this file except in compliance with the License.
+   You may obtain a copy of the License at
+
+       http://www.apache.org/licenses/LICENSE-2.0
+
+   Unless required by applicable law or agreed to in writing, software
+   distributed under the License is distributed on an "AS IS" BASIS,
+   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+   See the License for the specific language governing permissions and
+   limitations under the License.
+
+
+
+
+-----------------------------------------------------------------------------
+* javapoet.jar (com.squareup:javapoet:1.8.0)
+
+ ****** 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
+
+   APPENDIX: How to apply the Apache License to your work.
+
+      To apply the Apache License to your work, attach the following
+      boilerplate notice, with the fields enclosed by brackets "[]"
+      replaced with your own identifying information. (Don't include
+      the brackets!)  The text should be enclosed in the appropriate
+      comment syntax for the file format. We also recommend that a
+      file or class name and description of purpose be included on the
+      same "printed page" as the copyright notice for easier
+      identification within third-party archives.
+
+   Copyright [yyyy] [name of copyright owner]
+
+   Licensed under the Apache License, Version 2.0 (the "License");
+   you may not use this file except in compliance with the License.
+   You may obtain a copy of the License at
+
+       http://www.apache.org/licenses/LICENSE-2.0
+
+   Unless required by applicable law or agreed to in writing, software
+   distributed under the License is distributed on an "AS IS" BASIS,
+   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+   See the License for the specific language governing permissions and
+   limitations under the License.
+
+
+
+
+-----------------------------------------------------------------------------
+* antlr4.jar (org.antlr:antlr4:4.5.3)
+
+ ****** LICENSE:
+[The "BSD 3-clause license"]
+Copyright (c) 2012-2017 The ANTLR Project. All rights reserved.
+
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions
+are met:
+
+ 1. Redistributions of source code must retain the above copyright
+    notice, this list of conditions and the following disclaimer.
+ 2. Redistributions in binary form must reproduce the above copyright
+    notice, this list of conditions and the following disclaimer in the
+    documentation and/or other materials provided with the distribution.
+ 3. Neither the name of the copyright holder nor the names of its contributors
+    may be used to endorse or promote products derived from this software
+    without specific prior written permission.
+
+THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
+IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
+IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
+INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
+NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
+THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+=====
+
+MIT License for codepointat.js from https://git.io/codepointat
+MIT License for fromcodepoint.js from https://git.io/vDW1m
+
+Copyright Mathias Bynens <https://mathiasbynens.be/>
+
+Permission is hereby granted, free of charge, to any person obtaining
+a copy of this software and associated documentation files (the
+"Software"), to deal in the Software without restriction, including
+without limitation the rights to use, copy, modify, merge, publish,
+distribute, sublicense, and/or sell copies of the Software, and to
+permit persons to whom the Software is furnished to do so, subject to
+the following conditions:
+
+The above copyright notice and this permission notice shall be
+included in all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+
+
+
+
+-----------------------------------------------------------------------------
+* kotlin-stdlib.jar (org.jetbrains.kotlin:kotlin-stdlib:1.1.1)
+
+ ****** NOTICE:
+   =========================================================================
+   ==  NOTICE file corresponding to the section 4 d of                    ==
+   ==  the Apache License, Version 2.0,                                   ==
+   ==  in this case for the Kotlin Compiler distribution.                 ==
+   =========================================================================
+
+   Kotlin Compiler
+   Copyright 2010-2015 JetBrains s.r.o and respective authors and developers
+
+ ****** LICENSE:
+/*
+ * Copyright 2010-2017 JetBrains s.r.o.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+ ****** 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
+
+   APPENDIX: How to apply the Apache License to your work.
+
+      To apply the Apache License to your work, attach the following
+      boilerplate notice, with the fields enclosed by brackets "[]"
+      replaced with your own identifying information. (Don't include
+      the brackets!)  The text should be enclosed in the appropriate
+      comment syntax for the file format. We also recommend that a
+      file or class name and description of purpose be included on the
+      same "printed page" as the copyright notice for easier
+      identification within third-party archives.
+
+   Copyright [yyyy] [name of copyright owner]
+
+   Licensed under the Apache License, Version 2.0 (the "License");
+   you may not use this file except in compliance with the License.
+   You may obtain a copy of the License at
+
+       http://www.apache.org/licenses/LICENSE-2.0
+
+   Unless required by applicable law or agreed to in writing, software
+   distributed under the License is distributed on an "AS IS" BASIS,
+   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+   See the License for the specific language governing permissions and
+   limitations under the License.
+
+
+
+
+-----------------------------------------------------------------------------
+* auto-common.jar (com.google.auto:auto-common:0.6)
+
+ ****** 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
+
+   APPENDIX: How to apply the Apache License to your work.
+
+      To apply the Apache License to your work, attach the following
+      boilerplate notice, with the fields enclosed by brackets "[]"
+      replaced with your own identifying information. (Don't include
+      the brackets!)  The text should be enclosed in the appropriate
+      comment syntax for the file format. We also recommend that a
+      file or class name and description of purpose be included on the
+      same "printed page" as the copyright notice for easier
+      identification within third-party archives.
+
+   Copyright [yyyy] [name of copyright owner]
+
+   Licensed under the Apache License, Version 2.0 (the "License");
+   you may not use this file except in compliance with the License.
+   You may obtain a copy of the License at
+
+       http://www.apache.org/licenses/LICENSE-2.0
+
+   Unless required by applicable law or agreed to in writing, software
+   distributed under the License is distributed on an "AS IS" BASIS,
+   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+   See the License for the specific language governing permissions and
+   limitations under the License.
+
+
+
+
+-----------------------------------------------------------------------------
+* annotations.jar (org.jetbrains:annotations:13.0)
+
+ ****** 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
+
+   APPENDIX: How to apply the Apache License to your work.
+
+      To apply the Apache License to your work, attach the following
+      boilerplate notice, with the fields enclosed by brackets "[]"
+      replaced with your own identifying information. (Don't include
+      the brackets!)  The text should be enclosed in the appropriate
+      comment syntax for the file format. We also recommend that a
+      file or class name and description of purpose be included on the
+      same "printed page" as the copyright notice for easier
+      identification within third-party archives.
+
+   Copyright [yyyy] [name of copyright owner]
+
+   Licensed under the Apache License, Version 2.0 (the "License");
+   you may not use this file except in compliance with the License.
+   You may obtain a copy of the License at
+
+       http://www.apache.org/licenses/LICENSE-2.0
+
+   Unless required by applicable law or agreed to in writing, software
+   distributed under the License is distributed on an "AS IS" BASIS,
+   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+   See the License for the specific language governing permissions and
+   limitations under the License.
+
+
+
+
+-----------------------------------------------------------------------------
+* gson.jar (com.google.code.gson:gson:2.8.0)
+
+ ****** 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
+
+   APPENDIX: How to apply the Apache License to your work.
+
+      To apply the Apache License to your work, attach the following
+      boilerplate notice, with the fields enclosed by brackets "[]"
+      replaced with your own identifying information. (Don't include
+      the brackets!)  The text should be enclosed in the appropriate
+      comment syntax for the file format. We also recommend that a
+      file or class name and description of purpose be included on the
+      same "printed page" as the copyright notice for easier
+      identification within third-party archives.
+
+   Copyright [yyyy] [name of copyright owner]
+
+   Licensed under the Apache License, Version 2.0 (the "License");
+   you may not use this file except in compliance with the License.
+   You may obtain a copy of the License at
+
+       http://www.apache.org/licenses/LICENSE-2.0
+
+   Unless required by applicable law or agreed to in writing, software
+   distributed under the License is distributed on an "AS IS" BASIS,
+   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+   See the License for the specific language governing permissions and
+   limitations under the License.
+
+
+
+
+-----------------------------------------------------------------------------
+* sqlite-jdbc.jar (org.xerial:sqlite-jdbc:3.16.1)
+
+ ****** 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

+

+   APPENDIX: How to apply the Apache License to your work.

+

+      To apply the Apache License to your work, attach the following

+      boilerplate notice, with the fields enclosed by brackets "[]"

+      replaced with your own identifying information. (Don't include

+      the brackets!)  The text should be enclosed in the appropriate

+      comment syntax for the file format. We also recommend that a

+      file or class name and description of purpose be included on the

+      same "printed page" as the copyright notice for easier

+      identification within third-party archives.

+

+   Copyright [yyyy] [name of copyright owner]

+

+   Licensed under the Apache License, Version 2.0 (the "License");

+   you may not use this file except in compliance with the License.

+   You may obtain a copy of the License at

+

+       http://www.apache.org/licenses/LICENSE-2.0

+

+   Unless required by applicable law or agreed to in writing, software

+   distributed under the License is distributed on an "AS IS" BASIS,

+   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.

+   See the License for the specific language governing permissions and

+   limitations under the License.

+
+ ****** LICENSE:
+Copyright (c) 2006, David Crawshaw.  All rights reserved.

+

+Redistribution and use in source and binary forms, with or without

+modification, are permitted provided that the following conditions

+are met:

+

+1. Redistributions of source code must retain the above copyright

+   notice, this list of conditions and the following disclaimer.

+2. Redistributions in binary form must reproduce the above copyright

+   notice, this list of conditions and the following disclaimer in the

+   documentation and/or other materials provided with the distribution.

+

+THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND

+ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE

+IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE

+ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE

+FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL

+DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS

+OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)

+HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT

+LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY

+OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF

+SUCH DAMAGE.

+

+
+
+
+
+-----------------------------------------------------------------------------
+* guava.jar (com.google.guava:guava:18.0)
+
+ ****** 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
+
+   APPENDIX: How to apply the Apache License to your work.
+
+      To apply the Apache License to your work, attach the following
+      boilerplate notice, with the fields enclosed by brackets "[]"
+      replaced with your own identifying information. (Don't include
+      the brackets!)  The text should be enclosed in the appropriate
+      comment syntax for the file format. We also recommend that a
+      file or class name and description of purpose be included on the
+      same "printed page" as the copyright notice for easier
+      identification within third-party archives.
+
+   Copyright [yyyy] [name of copyright owner]
+
+   Licensed under the Apache License, Version 2.0 (the "License");
+   you may not use this file except in compliance with the License.
+   You may obtain a copy of the License at
+
+       http://www.apache.org/licenses/LICENSE-2.0
+
+   Unless required by applicable law or agreed to in writing, software
+   distributed under the License is distributed on an "AS IS" BASIS,
+   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+   See the License for the specific language governing permissions and
+   limitations under the License.
+
+
+
diff --git a/room/compiler/src/test/data/IGNORE_CHECKSTYLE b/room/compiler/src/test/data/IGNORE_CHECKSTYLE
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/room/compiler/src/test/data/IGNORE_CHECKSTYLE
diff --git a/room/compiler/src/test/data/common/input/Book.java b/room/compiler/src/test/data/common/input/Book.java
new file mode 100644
index 0000000..00cf8b6
--- /dev/null
+++ b/room/compiler/src/test/data/common/input/Book.java
@@ -0,0 +1,24 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package foo.bar;
+import android.arch.persistence.room.*;
+@Entity
+public class Book {
+    @PrimaryKey
+    int bookId;
+    int uid;
+}
diff --git a/room/compiler/src/test/data/common/input/ComputableLiveData.java b/room/compiler/src/test/data/common/input/ComputableLiveData.java
new file mode 100644
index 0000000..f135244
--- /dev/null
+++ b/room/compiler/src/test/data/common/input/ComputableLiveData.java
@@ -0,0 +1,9 @@
+//ComputableLiveData interface for tests
+package android.arch.lifecycle;
+import android.arch.lifecycle.LiveData;
+public abstract class ComputableLiveData<T> {
+    public ComputableLiveData(){}
+    abstract protected T compute();
+    public LiveData<T> getLiveData() {return null;}
+    public void invalidate() {}
+}
diff --git a/room/compiler/src/test/data/common/input/LiveData.java b/room/compiler/src/test/data/common/input/LiveData.java
new file mode 100644
index 0000000..3aea6ac
--- /dev/null
+++ b/room/compiler/src/test/data/common/input/LiveData.java
@@ -0,0 +1,4 @@
+//LiveData interface for tests
+package android.arch.lifecycle;
+public class LiveData<T> {
+}
diff --git a/room/compiler/src/test/data/common/input/MultiPKeyEntity.java b/room/compiler/src/test/data/common/input/MultiPKeyEntity.java
new file mode 100644
index 0000000..dccb9dd
--- /dev/null
+++ b/room/compiler/src/test/data/common/input/MultiPKeyEntity.java
@@ -0,0 +1,23 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package foo.bar;
+import android.arch.persistence.room.*;
+@Entity(primaryKeys = {"name", "lastName"})
+public class MultiPKeyEntity {
+    String name;
+    String lastName;
+}
diff --git a/room/compiler/src/test/data/common/input/NotAnEntity.java b/room/compiler/src/test/data/common/input/NotAnEntity.java
new file mode 100644
index 0000000..90c1dd4
--- /dev/null
+++ b/room/compiler/src/test/data/common/input/NotAnEntity.java
@@ -0,0 +1,22 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package foo.bar;
+import android.arch.persistence.room.*;
+public class NotAnEntity {
+    int bookId;
+    int uid;
+}
diff --git a/room/compiler/src/test/data/common/input/Rx2Room.java b/room/compiler/src/test/data/common/input/Rx2Room.java
new file mode 100644
index 0000000..da474fd
--- /dev/null
+++ b/room/compiler/src/test/data/common/input/Rx2Room.java
@@ -0,0 +1,5 @@
+// mock rx2 helper
+package android.arch.persistence.room;
+
+class RxRoom {
+}
diff --git a/room/compiler/src/test/data/common/input/User.java b/room/compiler/src/test/data/common/input/User.java
new file mode 100644
index 0000000..ce487d3
--- /dev/null
+++ b/room/compiler/src/test/data/common/input/User.java
@@ -0,0 +1,34 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package foo.bar;
+import android.arch.persistence.room.*;
+@Entity
+public class User {
+    @PrimaryKey
+    int uid;
+    String name;
+    private String lastName;
+    @ColumnInfo(name = "ageColumn")
+    public int age;
+
+    public String getLastName() {
+        return lastName;
+    }
+    public void setLastName(String lastName) {
+        this.lastName = lastName;
+    }
+}
diff --git a/room/compiler/src/test/data/common/input/reactivestreams/Publisher.java b/room/compiler/src/test/data/common/input/reactivestreams/Publisher.java
new file mode 100644
index 0000000..4ecc9f5
--- /dev/null
+++ b/room/compiler/src/test/data/common/input/reactivestreams/Publisher.java
@@ -0,0 +1,4 @@
+// fake reactivestreams publisher
+package org.reactivestreams;
+public interface Publisher<T> {
+}
\ No newline at end of file
diff --git a/room/compiler/src/test/data/common/input/rxjava2/Flowable.java b/room/compiler/src/test/data/common/input/rxjava2/Flowable.java
new file mode 100644
index 0000000..2d9d4d0
--- /dev/null
+++ b/room/compiler/src/test/data/common/input/rxjava2/Flowable.java
@@ -0,0 +1,6 @@
+// fake rx flowable
+package io.reactivex;
+import org.reactivestreams.Publisher;
+
+public abstract class Flowable<T> implements Publisher<T> {
+}
\ No newline at end of file
diff --git a/room/compiler/src/test/data/daoWriter/input/ComplexDao.java b/room/compiler/src/test/data/daoWriter/input/ComplexDao.java
new file mode 100644
index 0000000..ea36fde
--- /dev/null
+++ b/room/compiler/src/test/data/daoWriter/input/ComplexDao.java
@@ -0,0 +1,65 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package foo.bar;
+import android.arch.persistence.room.*;
+import java.util.List;
+import android.arch.lifecycle.LiveData;
+
+@Dao
+abstract class ComplexDao {
+    static class FullName {
+        public int id;
+        public String fullName;
+    }
+
+    private final ComplexDatabase mDb;
+
+    public ComplexDao(ComplexDatabase db) {
+        mDb = db;
+    }
+
+    @Query("SELECT name || lastName as fullName, uid as id FROM user where uid = :id")
+    abstract public List<FullName> fullNames(int id);
+
+    @Query("SELECT * FROM user where uid = :id")
+    abstract public User getById(int id);
+
+    @Query("SELECT * FROM user where name LIKE :name AND lastName LIKE :lastName")
+    abstract public User findByName(String name, String lastName);
+
+    @Query("SELECT * FROM user where uid IN (:ids)")
+    abstract public List<User> loadAllByIds(int... ids);
+
+    @Query("SELECT ageColumn FROM user where uid = :id")
+    abstract int getAge(int id);
+
+    @Query("SELECT ageColumn FROM user where uid IN(:ids)")
+    abstract public int[] getAllAges(int... ids);
+
+    @Query("SELECT ageColumn FROM user where uid IN(:ids)")
+    abstract public List<Integer> getAllAgesAsList(List<Integer> ids);
+
+    @Query("SELECT * FROM user where uid = :id")
+    abstract public LiveData<User> getByIdLive(int id);
+
+    @Query("SELECT * FROM user where uid IN (:ids)")
+    abstract public LiveData<List<User>> loadUsersByIdsLive(int... ids);
+
+    @Query("SELECT ageColumn FROM user where uid IN(:ids1) OR uid IN (:ids2) OR uid IN (:ids3)")
+    abstract public List<Integer> getAllAgesAsList(List<Integer> ids1,
+            int[] ids2, int... ids3);
+}
diff --git a/room/compiler/src/test/data/daoWriter/input/DeletionDao.java b/room/compiler/src/test/data/daoWriter/input/DeletionDao.java
new file mode 100644
index 0000000..a531ad6
--- /dev/null
+++ b/room/compiler/src/test/data/daoWriter/input/DeletionDao.java
@@ -0,0 +1,48 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package foo.bar;
+import android.arch.persistence.room.*;
+import java.util.List;
+
+@Dao
+abstract interface DeletionDao {
+    @Delete
+    void deleteUser(User user);
+    @Delete
+    void deleteUsers(User user1, List<User> others);
+    @Delete
+    void deleteArrayOfUsers(User[] users);
+
+    @Delete
+    int deleteUserAndReturnCount(User user);
+    @Delete
+    int deleteUserAndReturnCount(User user1, List<User> others);
+    @Delete
+    int deleteUserAndReturnCount(User[] users);
+
+    @Delete
+    int multiPKey(MultiPKeyEntity entity);
+
+    @Query("DELETE FROM user where uid = :uid")
+    int deleteByUid(int uid);
+
+    @Query("DELETE FROM user where uid IN(:uid)")
+    int deleteByUidList(int... uid);
+
+    @Delete
+    void deleteUserAndBook(User user, Book book);
+}
diff --git a/room/compiler/src/test/data/daoWriter/input/UpdateDao.java b/room/compiler/src/test/data/daoWriter/input/UpdateDao.java
new file mode 100644
index 0000000..b9a80c1
--- /dev/null
+++ b/room/compiler/src/test/data/daoWriter/input/UpdateDao.java
@@ -0,0 +1,42 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package foo.bar;
+import android.arch.persistence.room.*;
+import java.util.List;
+
+@Dao
+abstract interface UpdateDao {
+    @Update
+    void updateUser(User user);
+    @Update
+    void updateUsers(User user1, List<User> others);
+    @Update
+    void updateArrayOfUsers(User[] users);
+
+    @Update
+    int updateUserAndReturnCount(User user);
+    @Update
+    int updateUserAndReturnCount(User user1, List<User> others);
+    @Update
+    int updateUserAndReturnCount(User[] users);
+
+    @Update
+    int multiPKey(MultiPKeyEntity entity);
+
+    @Update
+    void updateUserAndBook(User user, Book book);
+}
diff --git a/room/compiler/src/test/data/daoWriter/input/WriterDao.java b/room/compiler/src/test/data/daoWriter/input/WriterDao.java
new file mode 100644
index 0000000..e122479
--- /dev/null
+++ b/room/compiler/src/test/data/daoWriter/input/WriterDao.java
@@ -0,0 +1,31 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package foo.bar;
+import android.arch.persistence.room.*;
+import java.util.List;
+
+@Dao
+abstract interface WriterDao {
+    @Insert
+    void insertUser(User user);
+    @Insert
+    void insertUsers(User user1, List<User> others);
+    @Insert(onConflict=OnConflictStrategy.REPLACE)
+    void insertUsers(User[] users);
+    @Insert
+    void insertUserAndBook(User user, Book book);
+}
diff --git a/room/compiler/src/test/data/daoWriter/output/ComplexDao.java b/room/compiler/src/test/data/daoWriter/output/ComplexDao.java
new file mode 100644
index 0000000..61594d2
--- /dev/null
+++ b/room/compiler/src/test/data/daoWriter/output/ComplexDao.java
@@ -0,0 +1,419 @@
+package foo.bar;
+
+import android.arch.lifecycle.ComputableLiveData;
+import android.arch.lifecycle.LiveData;
+import android.arch.persistence.room.InvalidationTracker.Observer;
+import android.arch.persistence.room.RoomDatabase;
+import android.arch.persistence.room.RoomSQLiteQuery;
+import android.arch.persistence.room.util.StringUtil;
+import android.database.Cursor;
+import android.support.annotation.NonNull;
+import java.lang.Integer;
+import java.lang.Override;
+import java.lang.String;
+import java.lang.StringBuilder;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Set;
+
+public class ComplexDao_Impl extends ComplexDao {
+    private final RoomDatabase __db;
+
+    public ComplexDao_Impl(ComplexDatabase __db) {
+        super(__db);
+        this.__db = __db;
+    }
+
+    @Override
+    public List<ComplexDao.FullName> fullNames(int id) {
+        final String _sql = "SELECT name || lastName as fullName, uid as id FROM user where uid = ?";
+        final RoomSQLiteQuery _statement = RoomSQLiteQuery.acquire(_sql, 1);
+        int _argIndex = 1;
+        _statement.bindLong(_argIndex, id);
+        final Cursor _cursor = __db.query(_statement);
+        try {
+            final int _cursorIndexOfFullName = _cursor.getColumnIndexOrThrow("fullName");
+            final int _cursorIndexOfId = _cursor.getColumnIndexOrThrow("id");
+            final List<ComplexDao.FullName> _result = new ArrayList<ComplexDao.FullName>(_cursor.getCount());
+            while(_cursor.moveToNext()) {
+                final ComplexDao.FullName _item;
+                _item = new ComplexDao.FullName();
+                _item.fullName = _cursor.getString(_cursorIndexOfFullName);
+                _item.id = _cursor.getInt(_cursorIndexOfId);
+                _result.add(_item);
+            }
+            return _result;
+        } finally {
+            _cursor.close();
+            _statement.release();
+        }
+    }
+
+    @Override
+    public User getById(int id) {
+        final String _sql = "SELECT * FROM user where uid = ?";
+        final RoomSQLiteQuery _statement = RoomSQLiteQuery.acquire(_sql, 1);
+        int _argIndex = 1;
+        _statement.bindLong(_argIndex, id);
+        final Cursor _cursor = __db.query(_statement);
+        try {
+            final int _cursorIndexOfUid = _cursor.getColumnIndexOrThrow("uid");
+            final int _cursorIndexOfName = _cursor.getColumnIndexOrThrow("name");
+            final int _cursorIndexOfLastName = _cursor.getColumnIndexOrThrow("lastName");
+            final int _cursorIndexOfAge = _cursor.getColumnIndexOrThrow("ageColumn");
+            final User _result;
+            if(_cursor.moveToFirst()) {
+                _result = new User();
+                _result.uid = _cursor.getInt(_cursorIndexOfUid);
+                _result.name = _cursor.getString(_cursorIndexOfName);
+                final String _tmpLastName;
+                _tmpLastName = _cursor.getString(_cursorIndexOfLastName);
+                _result.setLastName(_tmpLastName);
+                _result.age = _cursor.getInt(_cursorIndexOfAge);
+            } else {
+                _result = null;
+            }
+            return _result;
+        } finally {
+            _cursor.close();
+            _statement.release();
+        }
+    }
+
+    @Override
+    public User findByName(String name, String lastName) {
+        final String _sql = "SELECT * FROM user where name LIKE ? AND lastName LIKE ?";
+        final RoomSQLiteQuery _statement = RoomSQLiteQuery.acquire(_sql, 2);
+        int _argIndex = 1;
+        if (name == null) {
+            _statement.bindNull(_argIndex);
+        } else {
+            _statement.bindString(_argIndex, name);
+        }
+        _argIndex = 2;
+        if (lastName == null) {
+            _statement.bindNull(_argIndex);
+        } else {
+            _statement.bindString(_argIndex, lastName);
+        }
+        final Cursor _cursor = __db.query(_statement);
+        try {
+            final int _cursorIndexOfUid = _cursor.getColumnIndexOrThrow("uid");
+            final int _cursorIndexOfName = _cursor.getColumnIndexOrThrow("name");
+            final int _cursorIndexOfLastName = _cursor.getColumnIndexOrThrow("lastName");
+            final int _cursorIndexOfAge = _cursor.getColumnIndexOrThrow("ageColumn");
+            final User _result;
+            if(_cursor.moveToFirst()) {
+                _result = new User();
+                _result.uid = _cursor.getInt(_cursorIndexOfUid);
+                _result.name = _cursor.getString(_cursorIndexOfName);
+                final String _tmpLastName;
+                _tmpLastName = _cursor.getString(_cursorIndexOfLastName);
+                _result.setLastName(_tmpLastName);
+                _result.age = _cursor.getInt(_cursorIndexOfAge);
+            } else {
+                _result = null;
+            }
+            return _result;
+        } finally {
+            _cursor.close();
+            _statement.release();
+        }
+    }
+
+    @Override
+    public List<User> loadAllByIds(int... ids) {
+        StringBuilder _stringBuilder = StringUtil.newStringBuilder();
+        _stringBuilder.append("SELECT * FROM user where uid IN (");
+        final int _inputSize = ids.length;
+        StringUtil.appendPlaceholders(_stringBuilder, _inputSize);
+        _stringBuilder.append(")");
+        final String _sql = _stringBuilder.toString();
+        final int _argCount = 0 + _inputSize;
+        final RoomSQLiteQuery _statement = RoomSQLiteQuery.acquire(_sql, _argCount);
+        int _argIndex = 1;
+        for (int _item : ids) {
+            _statement.bindLong(_argIndex, _item);
+            _argIndex ++;
+        }
+        final Cursor _cursor = __db.query(_statement);
+        try {
+            final int _cursorIndexOfUid = _cursor.getColumnIndexOrThrow("uid");
+            final int _cursorIndexOfName = _cursor.getColumnIndexOrThrow("name");
+            final int _cursorIndexOfLastName = _cursor.getColumnIndexOrThrow("lastName");
+            final int _cursorIndexOfAge = _cursor.getColumnIndexOrThrow("ageColumn");
+            final List<User> _result = new ArrayList<User>(_cursor.getCount());
+            while(_cursor.moveToNext()) {
+                final User _item_1;
+                _item_1 = new User();
+                _item_1.uid = _cursor.getInt(_cursorIndexOfUid);
+                _item_1.name = _cursor.getString(_cursorIndexOfName);
+                final String _tmpLastName;
+                _tmpLastName = _cursor.getString(_cursorIndexOfLastName);
+                _item_1.setLastName(_tmpLastName);
+                _item_1.age = _cursor.getInt(_cursorIndexOfAge);
+                _result.add(_item_1);
+            }
+            return _result;
+        } finally {
+            _cursor.close();
+            _statement.release();
+        }
+    }
+
+    @Override
+    int getAge(int id) {
+        final String _sql = "SELECT ageColumn FROM user where uid = ?";
+        final RoomSQLiteQuery _statement = RoomSQLiteQuery.acquire(_sql, 1);
+        int _argIndex = 1;
+        _statement.bindLong(_argIndex, id);
+        final Cursor _cursor = __db.query(_statement);
+        try {
+            final int _result;
+            if(_cursor.moveToFirst()) {
+                _result = _cursor.getInt(0);
+            } else {
+                _result = 0;
+            }
+            return _result;
+        } finally {
+            _cursor.close();
+            _statement.release();
+        }
+    }
+
+    @Override
+    public int[] getAllAges(int... ids) {
+        StringBuilder _stringBuilder = StringUtil.newStringBuilder();
+        _stringBuilder.append("SELECT ageColumn FROM user where uid IN(");
+        final int _inputSize = ids.length;
+        StringUtil.appendPlaceholders(_stringBuilder, _inputSize);
+        _stringBuilder.append(")");
+        final String _sql = _stringBuilder.toString();
+        final int _argCount = 0 + _inputSize;
+        final RoomSQLiteQuery _statement = RoomSQLiteQuery.acquire(_sql, _argCount);
+        int _argIndex = 1;
+        for (int _item : ids) {
+            _statement.bindLong(_argIndex, _item);
+            _argIndex ++;
+        }
+        final Cursor _cursor = __db.query(_statement);
+        try {
+            final int[] _result = new int[_cursor.getCount()];
+            int _index = 0;
+            while(_cursor.moveToNext()) {
+                final int _item_1;
+                _item_1 = _cursor.getInt(0);
+                _result[_index] = _item_1;
+                _index ++;
+            }
+            return _result;
+        } finally {
+            _cursor.close();
+            _statement.release();
+        }
+    }
+
+    @Override
+    public List<Integer> getAllAgesAsList(List<Integer> ids) {
+        StringBuilder _stringBuilder = StringUtil.newStringBuilder();
+        _stringBuilder.append("SELECT ageColumn FROM user where uid IN(");
+        final int _inputSize = ids.size();
+        StringUtil.appendPlaceholders(_stringBuilder, _inputSize);
+        _stringBuilder.append(")");
+        final String _sql = _stringBuilder.toString();
+        final int _argCount = 0 + _inputSize;
+        final RoomSQLiteQuery _statement = RoomSQLiteQuery.acquire(_sql, _argCount);
+        int _argIndex = 1;
+        for (Integer _item : ids) {
+            if (_item == null) {
+                _statement.bindNull(_argIndex);
+            } else {
+                _statement.bindLong(_argIndex, _item);
+            }
+            _argIndex ++;
+        }
+        final Cursor _cursor = __db.query(_statement);
+        try {
+            final List<Integer> _result = new ArrayList<Integer>(_cursor.getCount());
+            while(_cursor.moveToNext()) {
+                final Integer _item_1;
+                if (_cursor.isNull(0)) {
+                    _item_1 = null;
+                } else {
+                    _item_1 = _cursor.getInt(0);
+                }
+                _result.add(_item_1);
+            }
+            return _result;
+        } finally {
+            _cursor.close();
+            _statement.release();
+        }
+    }
+
+    @Override
+    public LiveData<User> getByIdLive(int id) {
+        final String _sql = "SELECT * FROM user where uid = ?";
+        final RoomSQLiteQuery _statement = RoomSQLiteQuery.acquire(_sql, 1);
+        int _argIndex = 1;
+        _statement.bindLong(_argIndex, id);
+        return new ComputableLiveData<User>() {
+            private Observer _observer;
+
+            @Override
+            protected User compute() {
+                if (_observer == null) {
+                    _observer = new Observer("user") {
+                        @Override
+                        public void onInvalidated(@NonNull Set<String> tables) {
+                            invalidate();
+                        }
+                    };
+                    __db.getInvalidationTracker().addWeakObserver(_observer);
+                }
+                final Cursor _cursor = __db.query(_statement);
+                try {
+                    final int _cursorIndexOfUid = _cursor.getColumnIndexOrThrow("uid");
+                    final int _cursorIndexOfName = _cursor.getColumnIndexOrThrow("name");
+                    final int _cursorIndexOfLastName = _cursor.getColumnIndexOrThrow("lastName");
+                    final int _cursorIndexOfAge = _cursor.getColumnIndexOrThrow("ageColumn");
+                    final User _result;
+                    if(_cursor.moveToFirst()) {
+                        _result = new User();
+                        _result.uid = _cursor.getInt(_cursorIndexOfUid);
+                        _result.name = _cursor.getString(_cursorIndexOfName);
+                        final String _tmpLastName;
+                        _tmpLastName = _cursor.getString(_cursorIndexOfLastName);
+                        _result.setLastName(_tmpLastName);
+                        _result.age = _cursor.getInt(_cursorIndexOfAge);
+                    } else {
+                        _result = null;
+                    }
+                    return _result;
+                } finally {
+                    _cursor.close();
+                }
+            }
+
+            @Override
+            protected void finalize() {
+                _statement.release();
+            }
+        }.getLiveData();
+    }
+
+    @Override
+    public LiveData<List<User>> loadUsersByIdsLive(int... ids) {
+        StringBuilder _stringBuilder = StringUtil.newStringBuilder();
+        _stringBuilder.append("SELECT * FROM user where uid IN (");
+        final int _inputSize = ids.length;
+        StringUtil.appendPlaceholders(_stringBuilder, _inputSize);
+        _stringBuilder.append(")");
+        final String _sql = _stringBuilder.toString();
+        final int _argCount = 0 + _inputSize;
+        final RoomSQLiteQuery _statement = RoomSQLiteQuery.acquire(_sql, _argCount);
+        int _argIndex = 1;
+        for (int _item : ids) {
+            _statement.bindLong(_argIndex, _item);
+            _argIndex ++;
+        }
+        return new ComputableLiveData<List<User>>() {
+            private Observer _observer;
+
+            @Override
+            protected List<User> compute() {
+                if (_observer == null) {
+                    _observer = new Observer("user") {
+                        @Override
+                        public void onInvalidated(@NonNull Set<String> tables) {
+                            invalidate();
+                        }
+                    };
+                    __db.getInvalidationTracker().addWeakObserver(_observer);
+                }
+                final Cursor _cursor = __db.query(_statement);
+                try {
+                    final int _cursorIndexOfUid = _cursor.getColumnIndexOrThrow("uid");
+                    final int _cursorIndexOfName = _cursor.getColumnIndexOrThrow("name");
+                    final int _cursorIndexOfLastName = _cursor.getColumnIndexOrThrow("lastName");
+                    final int _cursorIndexOfAge = _cursor.getColumnIndexOrThrow("ageColumn");
+                    final List<User> _result = new ArrayList<User>(_cursor.getCount());
+                    while(_cursor.moveToNext()) {
+                        final User _item_1;
+                        _item_1 = new User();
+                        _item_1.uid = _cursor.getInt(_cursorIndexOfUid);
+                        _item_1.name = _cursor.getString(_cursorIndexOfName);
+                        final String _tmpLastName;
+                        _tmpLastName = _cursor.getString(_cursorIndexOfLastName);
+                        _item_1.setLastName(_tmpLastName);
+                        _item_1.age = _cursor.getInt(_cursorIndexOfAge);
+                        _result.add(_item_1);
+                    }
+                    return _result;
+                } finally {
+                    _cursor.close();
+                }
+            }
+
+            @Override
+            protected void finalize() {
+                _statement.release();
+            }
+        }.getLiveData();
+    }
+
+    @Override
+    public List<Integer> getAllAgesAsList(List<Integer> ids1, int[] ids2, int... ids3) {
+        StringBuilder _stringBuilder = StringUtil.newStringBuilder();
+        _stringBuilder.append("SELECT ageColumn FROM user where uid IN(");
+        final int _inputSize = ids1.size();
+        StringUtil.appendPlaceholders(_stringBuilder, _inputSize);
+        _stringBuilder.append(") OR uid IN (");
+        final int _inputSize_1 = ids2.length;
+        StringUtil.appendPlaceholders(_stringBuilder, _inputSize_1);
+        _stringBuilder.append(") OR uid IN (");
+        final int _inputSize_2 = ids3.length;
+        StringUtil.appendPlaceholders(_stringBuilder, _inputSize_2);
+        _stringBuilder.append(")");
+        final String _sql = _stringBuilder.toString();
+        final int _argCount = 0 + _inputSize + _inputSize_1 + _inputSize_2;
+        final RoomSQLiteQuery _statement = RoomSQLiteQuery.acquire(_sql, _argCount);
+        int _argIndex = 1;
+        for (Integer _item : ids1) {
+            if (_item == null) {
+                _statement.bindNull(_argIndex);
+            } else {
+                _statement.bindLong(_argIndex, _item);
+            }
+            _argIndex ++;
+        }
+        _argIndex = 1 + _inputSize;
+        for (int _item_1 : ids2) {
+            _statement.bindLong(_argIndex, _item_1);
+            _argIndex ++;
+        }
+        _argIndex = 1 + _inputSize + _inputSize_1;
+        for (int _item_2 : ids3) {
+            _statement.bindLong(_argIndex, _item_2);
+            _argIndex ++;
+        }
+        final Cursor _cursor = __db.query(_statement);
+        try {
+            final List<Integer> _result = new ArrayList<Integer>(_cursor.getCount());
+            while(_cursor.moveToNext()) {
+                final Integer _item_3;
+                if (_cursor.isNull(0)) {
+                    _item_3 = null;
+                } else {
+                    _item_3 = _cursor.getInt(0);
+                }
+                _result.add(_item_3);
+            }
+            return _result;
+        } finally {
+            _cursor.close();
+            _statement.release();
+        }
+    }
+}
diff --git a/room/compiler/src/test/data/daoWriter/output/DeletionDao.java b/room/compiler/src/test/data/daoWriter/output/DeletionDao.java
new file mode 100644
index 0000000..e0487a0
--- /dev/null
+++ b/room/compiler/src/test/data/daoWriter/output/DeletionDao.java
@@ -0,0 +1,215 @@
+package foo.bar;
+
+import android.arch.persistence.db.SupportSQLiteStatement;
+import android.arch.persistence.room.EntityDeletionOrUpdateAdapter;
+import android.arch.persistence.room.RoomDatabase;
+import android.arch.persistence.room.SharedSQLiteStatement;
+import android.arch.persistence.room.util.StringUtil;
+import java.lang.Override;
+import java.lang.String;
+import java.lang.StringBuilder;
+import java.util.List;
+
+public class DeletionDao_Impl implements DeletionDao {
+  private final RoomDatabase __db;
+
+  private final EntityDeletionOrUpdateAdapter __deletionAdapterOfUser;
+
+  private final EntityDeletionOrUpdateAdapter __deletionAdapterOfMultiPKeyEntity;
+
+  private final EntityDeletionOrUpdateAdapter __deletionAdapterOfBook;
+
+  private final SharedSQLiteStatement __preparedStmtOfDeleteByUid;
+
+  public DeletionDao_Impl(RoomDatabase __db) {
+    this.__db = __db;
+    this.__deletionAdapterOfUser = new EntityDeletionOrUpdateAdapter<User>(__db) {
+      @Override
+      public String createQuery() {
+        return "DELETE FROM `User` WHERE `uid` = ?";
+      }
+
+      @Override
+      public void bind(SupportSQLiteStatement stmt, User value) {
+        stmt.bindLong(1, value.uid);
+      }
+    };
+    this.__deletionAdapterOfMultiPKeyEntity = new EntityDeletionOrUpdateAdapter<MultiPKeyEntity>(__db) {
+      @Override
+      public String createQuery() {
+        return "DELETE FROM `MultiPKeyEntity` WHERE `name` = ? AND `lastName` = ?";
+      }
+
+      @Override
+      public void bind(SupportSQLiteStatement stmt, MultiPKeyEntity value) {
+        if (value.name == null) {
+          stmt.bindNull(1);
+        } else {
+          stmt.bindString(1, value.name);
+        }
+        if (value.lastName == null) {
+          stmt.bindNull(2);
+        } else {
+          stmt.bindString(2, value.lastName);
+        }
+      }
+    };
+    this.__deletionAdapterOfBook = new EntityDeletionOrUpdateAdapter<Book>(__db) {
+      @Override
+      public String createQuery() {
+        return "DELETE FROM `Book` WHERE `bookId` = ?";
+      }
+
+      @Override
+      public void bind(SupportSQLiteStatement stmt, Book value) {
+        stmt.bindLong(1, value.bookId);
+      }
+    };
+    this.__preparedStmtOfDeleteByUid = new SharedSQLiteStatement(__db) {
+      @Override
+      public String createQuery() {
+        final String _query = "DELETE FROM user where uid = ?";
+        return _query;
+      }
+    };
+  }
+
+  @Override
+  public void deleteUser(User user) {
+    __db.beginTransaction();
+    try {
+      __deletionAdapterOfUser.handle(user);
+      __db.setTransactionSuccessful();
+    } finally {
+      __db.endTransaction();
+    }
+  }
+
+  @Override
+  public void deleteUsers(User user1, List<User> others) {
+    __db.beginTransaction();
+    try {
+      __deletionAdapterOfUser.handle(user1);
+      __deletionAdapterOfUser.handleMultiple(others);
+      __db.setTransactionSuccessful();
+    } finally {
+      __db.endTransaction();
+    }
+  }
+
+  @Override
+  public void deleteArrayOfUsers(User[] users) {
+    __db.beginTransaction();
+    try {
+      __deletionAdapterOfUser.handleMultiple(users);
+      __db.setTransactionSuccessful();
+    } finally {
+      __db.endTransaction();
+    }
+  }
+
+  @Override
+  public int deleteUserAndReturnCount(User user) {
+    int _total = 0;
+    __db.beginTransaction();
+    try {
+      _total +=__deletionAdapterOfUser.handle(user);
+      __db.setTransactionSuccessful();
+      return _total;
+    } finally {
+      __db.endTransaction();
+    }
+  }
+
+  @Override
+  public int deleteUserAndReturnCount(User user1, List<User> others) {
+    int _total = 0;
+    __db.beginTransaction();
+    try {
+      _total +=__deletionAdapterOfUser.handle(user1);
+      _total +=__deletionAdapterOfUser.handleMultiple(others);
+      __db.setTransactionSuccessful();
+      return _total;
+    } finally {
+      __db.endTransaction();
+    }
+  }
+
+  @Override
+  public int deleteUserAndReturnCount(User[] users) {
+    int _total = 0;
+    __db.beginTransaction();
+    try {
+      _total +=__deletionAdapterOfUser.handleMultiple(users);
+      __db.setTransactionSuccessful();
+      return _total;
+    } finally {
+      __db.endTransaction();
+    }
+  }
+
+  @Override
+  public int multiPKey(MultiPKeyEntity entity) {
+    int _total = 0;
+    __db.beginTransaction();
+    try {
+      _total +=__deletionAdapterOfMultiPKeyEntity.handle(entity);
+      __db.setTransactionSuccessful();
+      return _total;
+    } finally {
+      __db.endTransaction();
+    }
+  }
+
+  @Override
+  public void deleteUserAndBook(User user, Book book) {
+    __db.beginTransaction();
+    try {
+      __deletionAdapterOfUser.handle(user);
+      __deletionAdapterOfBook.handle(book);
+      __db.setTransactionSuccessful();
+    } finally {
+      __db.endTransaction();
+    }
+  }
+
+  @Override
+  public int deleteByUid(int uid) {
+    final SupportSQLiteStatement _stmt = __preparedStmtOfDeleteByUid.acquire();
+    __db.beginTransaction();
+    try {
+      int _argIndex = 1;
+      _stmt.bindLong(_argIndex, uid);
+      final int _result = _stmt.executeUpdateDelete();
+      __db.setTransactionSuccessful();
+      return _result;
+    } finally {
+      __db.endTransaction();
+      __preparedStmtOfDeleteByUid.release(_stmt);
+    }
+  }
+
+  @Override
+  public int deleteByUidList(int... uid) {
+    StringBuilder _stringBuilder = StringUtil.newStringBuilder();
+    _stringBuilder.append("DELETE FROM user where uid IN(");
+    final int _inputSize = uid.length;
+    StringUtil.appendPlaceholders(_stringBuilder, _inputSize);
+    _stringBuilder.append(")");
+    final String _sql = _stringBuilder.toString();
+    SupportSQLiteStatement _stmt = __db.compileStatement(_sql);
+    int _argIndex = 1;
+    for (int _item : uid) {
+      _stmt.bindLong(_argIndex, _item);
+      _argIndex ++;
+    }
+    __db.beginTransaction();
+    try {
+      final int _result = _stmt.executeUpdateDelete();
+      __db.setTransactionSuccessful();
+      return _result;
+    } finally {
+      __db.endTransaction();
+    }
+  }
+}
diff --git a/room/compiler/src/test/data/daoWriter/output/UpdateDao.java b/room/compiler/src/test/data/daoWriter/output/UpdateDao.java
new file mode 100644
index 0000000..38f8c59
--- /dev/null
+++ b/room/compiler/src/test/data/daoWriter/output/UpdateDao.java
@@ -0,0 +1,187 @@
+package foo.bar;
+
+import android.arch.persistence.db.SupportSQLiteStatement;
+import android.arch.persistence.room.EntityDeletionOrUpdateAdapter;
+import android.arch.persistence.room.RoomDatabase;
+import java.lang.Override;
+import java.lang.String;
+import java.util.List;
+
+public class UpdateDao_Impl implements UpdateDao {
+  private final RoomDatabase __db;
+
+  private final EntityDeletionOrUpdateAdapter __updateAdapterOfUser;
+
+  private final EntityDeletionOrUpdateAdapter __updateAdapterOfMultiPKeyEntity;
+
+  private final EntityDeletionOrUpdateAdapter __updateAdapterOfBook;
+
+  public UpdateDao_Impl(RoomDatabase __db) {
+    this.__db = __db;
+    this.__updateAdapterOfUser = new EntityDeletionOrUpdateAdapter<User>(__db) {
+      @Override
+      public String createQuery() {
+        return "UPDATE OR ABORT `User` SET `uid` = ?,`name` = ?,`lastName` = ?,`ageColumn` = ? WHERE `uid` = ?";
+      }
+
+      @Override
+      public void bind(SupportSQLiteStatement stmt, User value) {
+        stmt.bindLong(1, value.uid);
+        if (value.name == null) {
+          stmt.bindNull(2);
+        } else {
+          stmt.bindString(2, value.name);
+        }
+        if (value.getLastName() == null) {
+          stmt.bindNull(3);
+        } else {
+          stmt.bindString(3, value.getLastName());
+        }
+        stmt.bindLong(4, value.age);
+        stmt.bindLong(5, value.uid);
+      }
+    };
+    this.__updateAdapterOfMultiPKeyEntity = new EntityDeletionOrUpdateAdapter<MultiPKeyEntity>(__db) {
+      @Override
+      public String createQuery() {
+        return "UPDATE OR ABORT `MultiPKeyEntity` SET `name` = ?,`lastName` = ? WHERE `name` = ? AND `lastName` = ?";
+      }
+
+      @Override
+      public void bind(SupportSQLiteStatement stmt, MultiPKeyEntity value) {
+        if (value.name == null) {
+          stmt.bindNull(1);
+        } else {
+          stmt.bindString(1, value.name);
+        }
+        if (value.lastName == null) {
+          stmt.bindNull(2);
+        } else {
+          stmt.bindString(2, value.lastName);
+        }
+        if (value.name == null) {
+          stmt.bindNull(3);
+        } else {
+          stmt.bindString(3, value.name);
+        }
+        if (value.lastName == null) {
+          stmt.bindNull(4);
+        } else {
+          stmt.bindString(4, value.lastName);
+        }
+      }
+    };
+    this.__updateAdapterOfBook = new EntityDeletionOrUpdateAdapter<Book>(__db) {
+      @Override
+      public String createQuery() {
+        return "UPDATE OR ABORT `Book` SET `bookId` = ?,`uid` = ? WHERE `bookId` = ?";
+      }
+
+      @Override
+      public void bind(SupportSQLiteStatement stmt, Book value) {
+        stmt.bindLong(1, value.bookId);
+        stmt.bindLong(2, value.uid);
+        stmt.bindLong(3, value.bookId);
+      }
+    };
+  }
+
+  @Override
+  public void updateUser(User user) {
+    __db.beginTransaction();
+    try {
+      __updateAdapterOfUser.handle(user);
+      __db.setTransactionSuccessful();
+    } finally {
+      __db.endTransaction();
+    }
+  }
+
+  @Override
+  public void updateUsers(User user1, List<User> others) {
+    __db.beginTransaction();
+    try {
+      __updateAdapterOfUser.handle(user1);
+      __updateAdapterOfUser.handleMultiple(others);
+      __db.setTransactionSuccessful();
+    } finally {
+      __db.endTransaction();
+    }
+  }
+
+  @Override
+  public void updateArrayOfUsers(User[] users) {
+    __db.beginTransaction();
+    try {
+      __updateAdapterOfUser.handleMultiple(users);
+      __db.setTransactionSuccessful();
+    } finally {
+      __db.endTransaction();
+    }
+  }
+
+  @Override
+  public int updateUserAndReturnCount(User user) {
+    int _total = 0;
+    __db.beginTransaction();
+    try {
+      _total +=__updateAdapterOfUser.handle(user);
+      __db.setTransactionSuccessful();
+      return _total;
+    } finally {
+      __db.endTransaction();
+    }
+  }
+
+  @Override
+  public int updateUserAndReturnCount(User user1, List<User> others) {
+    int _total = 0;
+    __db.beginTransaction();
+    try {
+      _total +=__updateAdapterOfUser.handle(user1);
+      _total +=__updateAdapterOfUser.handleMultiple(others);
+      __db.setTransactionSuccessful();
+      return _total;
+    } finally {
+      __db.endTransaction();
+    }
+  }
+
+  @Override
+  public int updateUserAndReturnCount(User[] users) {
+    int _total = 0;
+    __db.beginTransaction();
+    try {
+      _total +=__updateAdapterOfUser.handleMultiple(users);
+      __db.setTransactionSuccessful();
+      return _total;
+    } finally {
+      __db.endTransaction();
+    }
+  }
+
+  @Override
+  public int multiPKey(MultiPKeyEntity entity) {
+    int _total = 0;
+    __db.beginTransaction();
+    try {
+      _total +=__updateAdapterOfMultiPKeyEntity.handle(entity);
+      __db.setTransactionSuccessful();
+      return _total;
+    } finally {
+      __db.endTransaction();
+    }
+  }
+
+  @Override
+  public void updateUserAndBook(User user, Book book) {
+    __db.beginTransaction();
+    try {
+      __updateAdapterOfUser.handle(user);
+      __updateAdapterOfBook.handle(book);
+      __db.setTransactionSuccessful();
+    } finally {
+      __db.endTransaction();
+    }
+  }
+}
diff --git a/room/compiler/src/test/data/daoWriter/output/WriterDao.java b/room/compiler/src/test/data/daoWriter/output/WriterDao.java
new file mode 100644
index 0000000..76b9975
--- /dev/null
+++ b/room/compiler/src/test/data/daoWriter/output/WriterDao.java
@@ -0,0 +1,143 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package foo.bar;
+
+import android.arch.persistence.db.SupportSQLiteStatement;
+import android.arch.persistence.room.EntityInsertionAdapter;
+import android.arch.persistence.room.RoomDatabase;
+
+import java.lang.Override;
+import java.lang.String;
+import java.util.List;
+
+public class WriterDao_Impl implements WriterDao {
+    private final RoomDatabase __db;
+
+    private final EntityInsertionAdapter __insertionAdapterOfUser;
+
+    private final EntityInsertionAdapter __insertionAdapterOfUser_1;
+
+    private final EntityInsertionAdapter __insertionAdapterOfBook;
+
+    public WriterDao_Impl(RoomDatabase __db) {
+        this.__db = __db;
+        this.__insertionAdapterOfUser = new EntityInsertionAdapter<User>(__db) {
+            @Override
+            public String createQuery() {
+                return "INSERT OR ABORT INTO `User`(`uid`,`name`,`lastName`,`ageColumn`) VALUES"
+                        + " (?,?,?,?)";
+            }
+
+            @Override
+            public void bind(SupportSQLiteStatement stmt, User value) {
+                stmt.bindLong(1, value.uid);
+                if (value.name == null) {
+                    stmt.bindNull(2);
+                } else {
+                    stmt.bindString(2, value.name);
+                }
+                if (value.getLastName() == null) {
+                    stmt.bindNull(3);
+                } else {
+                    stmt.bindString(3, value.getLastName());
+                }
+                stmt.bindLong(4, value.age);
+            }
+        };
+        this.__insertionAdapterOfUser_1 = new EntityInsertionAdapter<User>(__db) {
+            @Override
+            public String createQuery() {
+                return "INSERT OR REPLACE INTO `User`(`uid`,`name`,`lastName`,`ageColumn`) VALUES"
+                        + " (?,?,?,?)";
+            }
+
+            @Override
+            public void bind(SupportSQLiteStatement stmt, User value) {
+                stmt.bindLong(1, value.uid);
+                if (value.name == null) {
+                    stmt.bindNull(2);
+                } else {
+                    stmt.bindString(2, value.name);
+                }
+                if (value.getLastName() == null) {
+                    stmt.bindNull(3);
+                } else {
+                    stmt.bindString(3, value.getLastName());
+                }
+                stmt.bindLong(4, value.age);
+            }
+        };
+        this.__insertionAdapterOfBook = new EntityInsertionAdapter<Book>(__db) {
+            @Override
+            public String createQuery() {
+                return "INSERT OR ABORT INTO `Book`(`bookId`,`uid`) VALUES (?,?)";
+            }
+
+            @Override
+            public void bind(SupportSQLiteStatement stmt, Book value) {
+                stmt.bindLong(1, value.bookId);
+                stmt.bindLong(2, value.uid);
+            }
+        };
+    }
+
+    @Override
+    public void insertUser(User user) {
+        __db.beginTransaction();
+        try {
+            __insertionAdapterOfUser.insert(user);
+            __db.setTransactionSuccessful();
+        } finally {
+            __db.endTransaction();
+        }
+    }
+
+    @Override
+    public void insertUsers(User user1, List<User> others) {
+        __db.beginTransaction();
+        try {
+            __insertionAdapterOfUser.insert(user1);
+            __insertionAdapterOfUser.insert(others);
+            __db.setTransactionSuccessful();
+        } finally {
+            __db.endTransaction();
+        }
+    }
+
+    @Override
+    public void insertUsers(User[] users) {
+        __db.beginTransaction();
+        try {
+            __insertionAdapterOfUser_1.insert(users);
+            __db.setTransactionSuccessful();
+        } finally {
+            __db.endTransaction();
+        }
+    }
+
+    @Override
+    public void insertUserAndBook(User user, Book book) {
+        __db.beginTransaction();
+        try {
+            __insertionAdapterOfUser.insert(user);
+            __insertionAdapterOfBook.insert(book);
+            __db.setTransactionSuccessful();
+        } finally {
+            __db.endTransaction();
+        }
+    }
+}
diff --git a/room/compiler/src/test/data/databasewriter/input/ComplexDatabase.java b/room/compiler/src/test/data/databasewriter/input/ComplexDatabase.java
new file mode 100644
index 0000000..f35e0b8
--- /dev/null
+++ b/room/compiler/src/test/data/databasewriter/input/ComplexDatabase.java
@@ -0,0 +1,23 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package foo.bar;
+import android.arch.persistence.room.*;
+import java.util.List;
+@Database(entities = {User.class}, version = 1923)
+abstract class ComplexDatabase extends RoomDatabase {
+    abstract ComplexDao getComplexDao();
+}
diff --git a/room/compiler/src/test/data/databasewriter/output/ComplexDatabase.java b/room/compiler/src/test/data/databasewriter/output/ComplexDatabase.java
new file mode 100644
index 0000000..b7c4657
--- /dev/null
+++ b/room/compiler/src/test/data/databasewriter/output/ComplexDatabase.java
@@ -0,0 +1,83 @@
+package foo.bar;
+
+import android.arch.persistence.db.SupportSQLiteDatabase;
+import android.arch.persistence.db.SupportSQLiteOpenHelper;
+import android.arch.persistence.db.SupportSQLiteOpenHelper.Callback;
+import android.arch.persistence.db.SupportSQLiteOpenHelper.Configuration;
+import android.arch.persistence.room.DatabaseConfiguration;
+import android.arch.persistence.room.InvalidationTracker;
+import android.arch.persistence.room.RoomOpenHelper;
+import android.arch.persistence.room.RoomOpenHelper.Delegate;
+import android.arch.persistence.room.util.TableInfo;
+import android.arch.persistence.room.util.TableInfo.Column;
+import android.arch.persistence.room.util.TableInfo.ForeignKey;
+import java.lang.IllegalStateException;
+import java.lang.Override;
+import java.lang.String;
+import java.util.HashMap;
+import java.util.HashSet;
+
+public class ComplexDatabase_Impl extends ComplexDatabase {
+    private volatile ComplexDao _complexDao;
+
+    protected SupportSQLiteOpenHelper createOpenHelper(DatabaseConfiguration configuration) {
+        final SupportSQLiteOpenHelper.Callback _openCallback = new RoomOpenHelper(configuration, new RoomOpenHelper.Delegate() {
+            public void createAllTables(SupportSQLiteDatabase _db) {
+                _db.execSQL("CREATE TABLE IF NOT EXISTS `User` (`uid` INTEGER, `name` TEXT, `lastName` TEXT, `ageColumn` INTEGER, PRIMARY KEY(`uid`))");
+                _db.execSQL("CREATE TABLE IF NOT EXISTS room_master_table (id INTEGER PRIMARY KEY,identity_hash TEXT)");
+                _db.execSQL("INSERT OR REPLACE INTO room_master_table (id,identity_hash) VALUES(42, \"d4b1d59e1344d0db40fe2cd3fe64d02f\")");
+            }
+
+            public void dropAllTables(SupportSQLiteDatabase _db) {
+                _db.execSQL("DROP TABLE IF EXISTS `User`");
+            }
+
+            public void onOpen(SupportSQLiteDatabase _db) {
+                mDatabase = _db;
+                internalInitInvalidationTracker(_db);
+            }
+
+            protected void validateMigration(SupportSQLiteDatabase _db) {
+                final HashMap<String, TableInfo.Column> _columnsUser = new HashMap<String, TableInfo.Column>(4);
+                _columnsUser.put("uid", new TableInfo.Column("uid", "INTEGER", 1));
+                _columnsUser.put("name", new TableInfo.Column("name", "TEXT", 0));
+                _columnsUser.put("lastName", new TableInfo.Column("lastName", "TEXT", 0));
+                _columnsUser.put("ageColumn", new TableInfo.Column("ageColumn", "INTEGER", 0));
+                final HashSet<TableInfo.ForeignKey> _foreignKeysUser = new HashSet<TableInfo.ForeignKey>(0);
+                final TableInfo _infoUser = new TableInfo("User", _columnsUser, _foreignKeysUser);
+                final TableInfo _existingUser = TableInfo.read(_db, "User");
+                if (! _infoUser.equals(_existingUser)) {
+                    throw new IllegalStateException("Migration didn't properly handle User(foo.bar.User).\n"
+                            + " Expected:\n" + _infoUser + "\n"
+                            + " Found:\n" + _existingUser);
+                }
+            }
+        }, "d4b1d59e1344d0db40fe2cd3fe64d02f");
+        final SupportSQLiteOpenHelper.Configuration _sqliteConfig = SupportSQLiteOpenHelper.Configuration.builder(configuration.context)
+                .name(configuration.name)
+                .version(1923)
+                .callback(_openCallback)
+                .build();
+        final SupportSQLiteOpenHelper _helper = configuration.sqliteOpenHelperFactory.create(_sqliteConfig);
+        return _helper;
+    }
+
+    @Override
+    protected InvalidationTracker createInvalidationTracker() {
+        return new InvalidationTracker(this, "User");
+    }
+
+    @Override
+    ComplexDao getComplexDao() {
+        if (_complexDao != null) {
+            return _complexDao;
+        } else {
+            synchronized(this) {
+                if(_complexDao == null) {
+                    _complexDao = new ComplexDao_Impl(this);
+                }
+                return _complexDao;
+            }
+        }
+    }
+}
diff --git a/room/compiler/src/test/kotlin/android/arch/persistence/room/parser/SqlParserTest.kt b/room/compiler/src/test/kotlin/android/arch/persistence/room/parser/SqlParserTest.kt
new file mode 100644
index 0000000..68b1868
--- /dev/null
+++ b/room/compiler/src/test/kotlin/android/arch/persistence/room/parser/SqlParserTest.kt
@@ -0,0 +1,155 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.arch.persistence.room.parser
+
+import org.hamcrest.CoreMatchers.`is`
+import org.hamcrest.MatcherAssert.assertThat
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.junit.runners.JUnit4
+
+@RunWith(JUnit4::class)
+class SqlParserTest {
+
+    @Test
+    fun multipleQueries() {
+        assertErrors("SELECT * FROM users; SELECT * FROM books;",
+                ParserErrors.NOT_ONE_QUERY)
+    }
+
+    @Test
+    fun empty() {
+        assertErrors("", ParserErrors.NOT_ONE_QUERY)
+    }
+
+    @Test
+    fun deleteQuery() {
+        val parsed = SqlParser.parse("DELETE FROM users where id > 3")
+        assertThat(parsed.errors, `is`(emptyList()))
+        assertThat(parsed.type, `is`(QueryType.DELETE))
+    }
+
+    @Test
+    fun badDeleteQuery() {
+        assertErrors("delete from user where mAge >= :min && mAge <= :max",
+                "no viable alternative at input 'delete from user where mAge >= :min &&'")
+    }
+
+    @Test
+    fun updateQuery() {
+        val parsed = SqlParser.parse("UPDATE users set name = :name where id = :id")
+        assertThat(parsed.errors, `is`(emptyList()))
+        assertThat(parsed.type, `is`(QueryType.UPDATE))
+    }
+
+    @Test
+    fun explain() {
+        assertErrors("EXPLAIN QUERY PLAN SELECT * FROM users",
+                ParserErrors.invalidQueryType(QueryType.EXPLAIN))
+    }
+
+    @Test
+    fun extractTableNames() {
+        assertThat(SqlParser.parse("select * from users").tables,
+                `is`(setOf(Table("users", "users"))))
+        assertThat(SqlParser.parse("select * from users as ux").tables,
+                `is`(setOf(Table("users", "ux"))))
+        assertThat(SqlParser.parse("select * from (select * from books)").tables,
+                `is`(setOf(Table("books", "books"))))
+        assertThat(SqlParser.parse("select x.id from (select * from books) as x").tables,
+                `is`(setOf(Table("books", "books"))))
+    }
+
+    @Test
+    fun unescapeTableNames() {
+        assertThat(SqlParser.parse("select * from `users`").tables,
+                `is`(setOf(Table("users", "users"))))
+        assertThat(SqlParser.parse("select * from \"users\"").tables,
+                `is`(setOf(Table("users", "users"))))
+    }
+
+    @Test
+    fun findBindVariables() {
+        assertVariables("select * from users")
+        assertVariables("select * from users where name like ?", "?")
+        assertVariables("select * from users where name like :name", ":name")
+        assertVariables("select * from users where name like ?2", "?2")
+        assertVariables("select * from users where name like ?2 OR name LIKE ?1", "?2", "?1")
+        assertVariables("select * from users where name like @a", "@a")
+        assertVariables("select * from users where name like \$a", "\$a")
+    }
+
+    @Test
+    fun  indexedVariablesError() {
+        assertErrors("select * from users where name like ?",
+                ParserErrors.ANONYMOUS_BIND_ARGUMENT)
+        assertErrors("select * from users where name like ? or last_name like ?",
+                ParserErrors.ANONYMOUS_BIND_ARGUMENT)
+        assertErrors("select * from users where name like ?1",
+                ParserErrors.cannotUseVariableIndices("?1", 36))
+    }
+
+    @Test
+    fun foo() {
+        assertSections("select * from users where name like ?",
+                Section.text("select * from users where name like "),
+                Section.bindVar("?"))
+
+        assertSections("select * from users where name like :name AND last_name like :lastName",
+                Section.text("select * from users where name like "),
+                Section.bindVar(":name"),
+                Section.text(" AND last_name like "),
+                Section.bindVar(":lastName"))
+
+        assertSections("select * from users where name \nlike :name AND last_name like :lastName",
+                Section.text("select * from users where name "),
+                Section.newline(),
+                Section.text("like "),
+                Section.bindVar(":name"),
+                Section.text(" AND last_name like "),
+                Section.bindVar(":lastName"))
+
+        assertSections("select * from users where name like :name \nAND last_name like :lastName",
+                Section.text("select * from users where name like "),
+                Section.bindVar(":name"),
+                Section.text(" "),
+                Section.newline(),
+                Section.text("AND last_name like "),
+                Section.bindVar(":lastName"))
+
+        assertSections("select * from users where name like :name \nAND last_name like \n:lastName",
+                Section.text("select * from users where name like "),
+                Section.bindVar(":name"),
+                Section.text(" "),
+                Section.newline(),
+                Section.text("AND last_name like "),
+                Section.newline(),
+                Section.bindVar(":lastName"))
+    }
+
+    fun assertVariables(query: String, vararg expected: String) {
+        assertThat((SqlParser.parse(query)).inputs.map { it.text }, `is`(expected.toList()))
+    }
+
+    fun assertErrors(query: String, vararg errors: String) {
+        assertThat((SqlParser.parse(query)).errors, `is`(errors.toList()))
+    }
+
+    fun assertSections(query: String, vararg sections: Section) {
+        assertThat(SqlParser.parse(query).sections, `is`(sections.toList()))
+    }
+}
diff --git a/room/compiler/src/test/kotlin/android/arch/persistence/room/processor/BaseDaoTest.kt b/room/compiler/src/test/kotlin/android/arch/persistence/room/processor/BaseDaoTest.kt
new file mode 100644
index 0000000..ae648f7
--- /dev/null
+++ b/room/compiler/src/test/kotlin/android/arch/persistence/room/processor/BaseDaoTest.kt
@@ -0,0 +1,169 @@
+package android.arch.persistence.room.processor
+
+import COMMON
+import android.arch.persistence.room.ext.RoomTypeNames
+import android.arch.persistence.room.vo.Dao
+import android.arch.persistence.room.writer.DaoWriter
+import com.google.auto.common.MoreTypes
+import com.google.testing.compile.JavaFileObjects
+import org.hamcrest.CoreMatchers.`is`
+import org.hamcrest.MatcherAssert.assertThat
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.junit.runners.JUnit4
+import simpleRun
+
+/**
+ * we don't assert much in these tests since if type resolution fails, compilation fails.
+ */
+@RunWith(JUnit4::class)
+class BaseDaoTest {
+    private fun String.toJFO(qName: String) = JavaFileObjects.forSourceLines(qName, this)
+
+    @Test
+    fun insert() {
+        baseDao("""
+            @Insert
+            void insertMe(T t);
+        """) { dao ->
+            assertThat(dao.insertionMethods.size, `is`(1))
+        }
+    }
+
+    @Test
+    fun insertArray() {
+        baseDao("""
+            @Insert
+            void insertMe(T[] t);
+        """) { dao ->
+            assertThat(dao.insertionMethods.size, `is`(1))
+        }
+    }
+
+    @Test
+    fun insertVarArg() {
+        baseDao("""
+            @Insert
+            void insertMe(T... t);
+        """) { dao ->
+            assertThat(dao.insertionMethods.size, `is`(1))
+        }
+    }
+
+    @Test
+    fun insertList() {
+        baseDao("""
+            @Insert
+            void insertMe(List<T> t);
+        """) { dao ->
+            assertThat(dao.insertionMethods.size, `is`(1))
+        }
+    }
+
+    @Test
+    fun delete() {
+        baseDao("""
+            @Delete
+            void deleteMe(T t);
+        """) { dao ->
+            assertThat(dao.deletionMethods.size, `is`(1))
+        }
+    }
+
+    @Test
+    fun deleteArray() {
+        baseDao("""
+            @Delete
+            void deleteMe(T[] t);
+        """) { dao ->
+            assertThat(dao.deletionMethods.size, `is`(1))
+        }
+    }
+
+    @Test
+    fun deleteVarArg() {
+        baseDao("""
+            @Delete
+            void deleteMe(T... t);
+        """) { dao ->
+            assertThat(dao.deletionMethods.size, `is`(1))
+        }
+    }
+
+    @Test
+    fun deleteList() {
+        baseDao("""
+            @Delete
+            void deleteMe(List<T> t);
+        """) { dao ->
+            assertThat(dao.deletionMethods.size, `is`(1))
+        }
+    }
+
+    @Test
+    fun update() {
+        baseDao("""
+            @Update
+            void updateMe(T t);
+        """) { dao ->
+            assertThat(dao.updateMethods.size, `is`(1))
+        }
+    }
+
+    @Test
+    fun updateArray() {
+        baseDao("""
+            @Update
+            void updateMe(T[] t);
+        """) { dao ->
+            assertThat(dao.updateMethods.size, `is`(1))
+        }
+    }
+
+    @Test
+    fun updateVarArg() {
+        baseDao("""
+            @Update
+            void updateMe(T... t);
+        """) { dao ->
+            assertThat(dao.updateMethods.size, `is`(1))
+        }
+    }
+
+    @Test
+    fun updateList() {
+        baseDao("""
+            @Update
+            void updateMe(List<T> t);
+        """) { dao ->
+            assertThat(dao.updateMethods.size, `is`(1))
+        }
+    }
+
+    fun baseDao(code : String, handler : (Dao) -> Unit) {
+        val baseClass = """
+            package foo.bar;
+            import android.arch.persistence.room.*;
+            import java.util.List;
+
+            interface BaseDao<K, T> {
+                $code
+            }
+        """.toJFO("foo.bar.BaseDao")
+        val extension = """
+            package foo.bar;
+            import android.arch.persistence.room.*;
+            @Dao
+            interface MyDao extends BaseDao<Integer, User> {
+            }
+        """.toJFO("foo.bar.MyDao")
+        simpleRun(baseClass, extension, COMMON.USER) { invocation ->
+            val daoElm = invocation.processingEnv.elementUtils.getTypeElement("foo.bar.MyDao")
+            val dbType = MoreTypes.asDeclared(invocation.context.processingEnv.elementUtils
+                    .getTypeElement(RoomTypeNames.ROOM_DB.toString()).asType())
+            val processedDao = DaoProcessor(invocation.context, daoElm, dbType, null).process()
+            handler(processedDao)
+            DaoWriter(processedDao, invocation.processingEnv).write(invocation.processingEnv)
+        }.compilesWithoutError()
+    }
+}
\ No newline at end of file
diff --git a/room/compiler/src/test/kotlin/android/arch/persistence/room/processor/BaseEntityParserTest.kt b/room/compiler/src/test/kotlin/android/arch/persistence/room/processor/BaseEntityParserTest.kt
new file mode 100644
index 0000000..d00ff0c
--- /dev/null
+++ b/room/compiler/src/test/kotlin/android/arch/persistence/room/processor/BaseEntityParserTest.kt
@@ -0,0 +1,84 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.arch.persistence.room.processor
+
+import android.arch.persistence.room.Embedded
+import android.arch.persistence.room.testing.TestInvocation
+import android.arch.persistence.room.testing.TestProcessor
+import android.arch.persistence.room.vo.Entity
+import com.google.auto.common.MoreElements
+import com.google.common.truth.Truth
+import com.google.testing.compile.CompileTester
+import com.google.testing.compile.JavaFileObjects
+import com.google.testing.compile.JavaSourceSubjectFactory
+import com.google.testing.compile.JavaSourcesSubjectFactory
+import javax.tools.JavaFileObject
+
+abstract class BaseEntityParserTest {
+    companion object {
+        const val ENTITY_PREFIX = """
+            package foo.bar;
+            import android.arch.persistence.room.*;
+            @Entity%s
+            public class MyEntity %s {
+            """
+        const val ENTITY_SUFFIX = "}"
+    }
+
+    fun singleEntity(input: String, attributes: Map<String, String> = mapOf(),
+                     baseClass : String = "",
+                     jfos : List<JavaFileObject> = emptyList(),
+                     handler: (Entity, TestInvocation) -> Unit): CompileTester {
+        val attributesReplacement : String
+        if (attributes.isEmpty()) {
+            attributesReplacement = ""
+        } else {
+            attributesReplacement = "(" +
+                    attributes.entries.map { "${it.key} = ${it.value}" }.joinToString(",") +
+                    ")".trimIndent()
+        }
+        val baseClassReplacement : String
+        if (baseClass == "") {
+            baseClassReplacement = ""
+        } else {
+            baseClassReplacement = " extends $baseClass"
+        }
+        return Truth.assertAbout(JavaSourcesSubjectFactory.javaSources())
+                .that(jfos + JavaFileObjects.forSourceString("foo.bar.MyEntity",
+                        ENTITY_PREFIX.format(attributesReplacement, baseClassReplacement)
+                                + input + ENTITY_SUFFIX
+                ))
+                .processedWith(TestProcessor.builder()
+                        .forAnnotations(android.arch.persistence.room.Entity::class,
+                                android.arch.persistence.room.PrimaryKey::class,
+                                android.arch.persistence.room.Ignore::class,
+                                Embedded::class,
+                                android.arch.persistence.room.ColumnInfo::class)
+                        .nextRunHandler { invocation ->
+                            val entity = invocation.roundEnv
+                                    .getElementsAnnotatedWith(
+                                            android.arch.persistence.room.Entity::class.java)
+                                    .first { it.toString() == "foo.bar.MyEntity" }
+                            val parser = EntityProcessor(invocation.context,
+                                    MoreElements.asType(entity))
+                            val parsedQuery = parser.process()
+                            handler(parsedQuery, invocation)
+                            true
+                        }
+                        .build())
+    }
+}
diff --git a/room/compiler/src/test/kotlin/android/arch/persistence/room/processor/CustomConverterProcessorTest.kt b/room/compiler/src/test/kotlin/android/arch/persistence/room/processor/CustomConverterProcessorTest.kt
new file mode 100644
index 0000000..34bbd24
--- /dev/null
+++ b/room/compiler/src/test/kotlin/android/arch/persistence/room/processor/CustomConverterProcessorTest.kt
@@ -0,0 +1,268 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.arch.persistence.room.processor
+
+import android.arch.persistence.room.TypeConverter
+import android.arch.persistence.room.ext.typeName
+import android.arch.persistence.room.processor.ProcessorErrors.TYPE_CONVERTER_EMPTY_CLASS
+import android.arch.persistence.room.processor.ProcessorErrors
+        .TYPE_CONVERTER_MISSING_NOARG_CONSTRUCTOR
+import android.arch.persistence.room.processor.ProcessorErrors.TYPE_CONVERTER_MUST_BE_PUBLIC
+import android.arch.persistence.room.processor.ProcessorErrors.TYPE_CONVERTER_UNBOUND_GENERIC
+import android.arch.persistence.room.testing.TestInvocation
+import android.arch.persistence.room.vo.CustomTypeConverter
+import com.google.testing.compile.CompileTester
+import com.google.testing.compile.JavaFileObjects
+import com.squareup.javapoet.ClassName
+import com.squareup.javapoet.MethodSpec
+import com.squareup.javapoet.ParameterSpec
+import com.squareup.javapoet.ParameterizedTypeName
+import com.squareup.javapoet.TypeName
+import com.squareup.javapoet.TypeSpec
+import com.squareup.javapoet.TypeVariableName
+import org.hamcrest.CoreMatchers.`is`
+import org.hamcrest.MatcherAssert.assertThat
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.junit.runners.JUnit4
+import simpleRun
+import java.util.Date
+import javax.lang.model.element.Modifier
+import javax.tools.JavaFileObject
+
+@RunWith(JUnit4::class)
+class CustomConverterProcessorTest {
+    companion object {
+        val CONVERTER = ClassName.get("foo.bar", "MyConverter")!!
+        val CONVERTER_QNAME = CONVERTER.packageName() + "." + CONVERTER.simpleName()
+        val CONTAINER = JavaFileObjects.forSourceString("foo.bar.Container",
+                """
+                package foo.bar;
+                import android.arch.persistence.room.*;
+                @TypeConverters(foo.bar.MyConverter.class)
+                public class Container {}
+                """)
+    }
+
+    @Test
+    fun validCase() {
+        singleClass(createConverter(TypeName.SHORT.box(), TypeName.CHAR.box()))
+        { converter, invocation ->
+            assertThat(converter?.fromTypeName, `is`(TypeName.SHORT.box()))
+            assertThat(converter?.toTypeName, `is`(TypeName.CHAR.box()))
+        }.compilesWithoutError()
+    }
+
+    @Test
+    fun primitiveFrom() {
+        singleClass(createConverter(TypeName.SHORT, TypeName.CHAR.box())) { converter, invocation ->
+            assertThat(converter?.fromTypeName, `is`(TypeName.SHORT))
+            assertThat(converter?.toTypeName, `is`(TypeName.CHAR.box()))
+        }.compilesWithoutError()
+    }
+
+    @Test
+    fun primitiveTo() {
+        singleClass(createConverter(TypeName.INT.box(), TypeName.DOUBLE)) { converter, invocation ->
+            assertThat(converter?.fromTypeName, `is`(TypeName.INT.box()))
+            assertThat(converter?.toTypeName, `is`(TypeName.DOUBLE))
+        }.compilesWithoutError()
+    }
+
+    @Test
+    fun primitiveBoth() {
+        singleClass(createConverter(TypeName.INT, TypeName.DOUBLE)) { converter, invocation ->
+            assertThat(converter?.fromTypeName, `is`(TypeName.INT))
+            assertThat(converter?.toTypeName, `is`(TypeName.DOUBLE))
+        }.compilesWithoutError()
+    }
+
+    @Test
+    fun nonNullButNotBoxed() {
+        val string = String::class.typeName()
+        val date = Date::class.typeName()
+        singleClass(createConverter(string, date)) { converter, invocation ->
+            assertThat(converter?.fromTypeName, `is`(string as TypeName))
+            assertThat(converter?.toTypeName, `is`(date as TypeName))
+        }
+    }
+
+    @Test
+    fun parametrizedTypeUnbound() {
+        val typeVarT = TypeVariableName.get("T")
+        val list = ParameterizedTypeName.get(List::class.typeName(), typeVarT)
+        val typeVarK = TypeVariableName.get("K")
+        val map = ParameterizedTypeName.get(Map::class.typeName(), typeVarK, typeVarT)
+        singleClass(createConverter(list, map, listOf(typeVarK, typeVarT))) {
+            converter, invocation ->
+        }.failsToCompile().withErrorContaining(TYPE_CONVERTER_UNBOUND_GENERIC)
+    }
+
+    @Test
+    fun parametrizedTypeSpecific() {
+        val string = String::class.typeName()
+        val date = Date::class.typeName()
+        val list = ParameterizedTypeName.get(List::class.typeName(), string)
+        val map = ParameterizedTypeName.get(Map::class.typeName(), string, date)
+        singleClass(createConverter(list, map)) { converter, invocation ->
+            assertThat(converter?.fromTypeName, `is`(list as TypeName))
+            assertThat(converter?.toTypeName, `is`(map as TypeName))
+        }.compilesWithoutError()
+    }
+
+    @Test
+    fun testNoConverters() {
+        singleClass(JavaFileObjects.forSourceString(CONVERTER_QNAME,
+                """
+                package ${CONVERTER.packageName()};
+                public class ${CONVERTER.simpleName()} {
+                }
+                """)) { converter, invocation ->
+        }.failsToCompile().withErrorContaining(TYPE_CONVERTER_EMPTY_CLASS)
+    }
+
+    @Test
+    fun checkNoArgConstructor() {
+        singleClass(JavaFileObjects.forSourceString(CONVERTER_QNAME,
+                """
+                package ${CONVERTER.packageName()};
+                import android.arch.persistence.room.TypeConverter;
+
+                public class ${CONVERTER.simpleName()} {
+                    public ${CONVERTER.simpleName()}(int x) {}
+                    @TypeConverter
+                    public int x(short y) {return 0;}
+                }
+                """)) { converter, invocation ->
+        }.failsToCompile().withErrorContaining(TYPE_CONVERTER_MISSING_NOARG_CONSTRUCTOR)
+    }
+
+    @Test
+    fun checkNoArgConstructor_withStatic() {
+        singleClass(JavaFileObjects.forSourceString(CONVERTER_QNAME,
+                """
+                package ${CONVERTER.packageName()};
+                import android.arch.persistence.room.TypeConverter;
+
+                public class ${CONVERTER.simpleName()} {
+                    public ${CONVERTER.simpleName()}(int x) {}
+                    @TypeConverter
+                    public static int x(short y) {return 0;}
+                }
+                """)) { converter, invocation ->
+            assertThat(converter?.fromTypeName, `is`(TypeName.SHORT))
+            assertThat(converter?.toTypeName, `is`(TypeName.INT))
+            assertThat(converter?.isStatic, `is`(true))
+        }.compilesWithoutError()
+    }
+
+    @Test
+    fun checkPublic() {
+        singleClass(JavaFileObjects.forSourceString(CONVERTER_QNAME,
+                """
+                package ${CONVERTER.packageName()};
+                import android.arch.persistence.room.TypeConverter;
+
+                public class ${CONVERTER.simpleName()} {
+                    @TypeConverter static int x(short y) {return 0;}
+                    @TypeConverter private static int y(boolean y) {return 0;}
+                }
+                """)) { converter, invocation ->
+            assertThat(converter?.fromTypeName, `is`(TypeName.SHORT))
+            assertThat(converter?.toTypeName, `is`(TypeName.INT))
+            assertThat(converter?.isStatic, `is`(true))
+        }.failsToCompile().withErrorContaining(TYPE_CONVERTER_MUST_BE_PUBLIC).and()
+                .withErrorCount(2)
+    }
+
+    @Suppress("PLATFORM_CLASS_MAPPED_TO_KOTLIN")
+    @Test
+    fun parametrizedTypeBoundViaParent() {
+        val typeVarT = TypeVariableName.get("T")
+        val list = ParameterizedTypeName.get(List::class.typeName(), typeVarT)
+        val typeVarK = TypeVariableName.get("K")
+        val map = ParameterizedTypeName.get(Map::class.typeName(), typeVarK, typeVarT)
+
+        val baseConverter = createConverter(list, map, listOf(typeVarT, typeVarK))
+        val extendingQName = "foo.bar.Extending"
+        val extendingClass = JavaFileObjects.forSourceString(extendingQName,
+                "package foo.bar;\n" +
+                        TypeSpec.classBuilder(ClassName.bestGuess(extendingQName)).apply {
+                            superclass(
+                                    ParameterizedTypeName.get(CONVERTER, String::class.typeName(),
+                                    Integer::class.typeName()))
+                        }.build().toString())
+
+        simpleRun(baseConverter, extendingClass) { invocation ->
+            val element = invocation.processingEnv.elementUtils.getTypeElement(extendingQName)
+            val converter = CustomConverterProcessor(invocation.context, element)
+                    .process().firstOrNull()
+            assertThat(converter?.fromTypeName, `is`(ParameterizedTypeName.get(
+                    List::class.typeName(), String::class.typeName()) as TypeName
+            ))
+            assertThat(converter?.toTypeName, `is`(ParameterizedTypeName.get(Map::class.typeName(),
+                    Integer::class.typeName(), String::class.typeName()) as TypeName
+            ))
+        }.compilesWithoutError()
+    }
+
+    @Test
+    fun checkDuplicates() {
+        singleClass(createConverter(TypeName.SHORT.box(), TypeName.CHAR.box(), duplicate = true))
+        { converter, invocation ->
+            assertThat(converter?.fromTypeName, `is`(TypeName.SHORT.box()))
+            assertThat(converter?.toTypeName, `is`(TypeName.CHAR.box()))
+        }.failsToCompile().withErrorContaining("Multiple methods define the same conversion")
+    }
+
+    private fun createConverter(from: TypeName, to: TypeName,
+                                typeVariables: List<TypeVariableName> = emptyList(),
+                                duplicate : Boolean = false)
+            : JavaFileObject {
+        val code = TypeSpec.classBuilder(CONVERTER).apply {
+            addTypeVariables(typeVariables)
+            addModifiers(Modifier.PUBLIC)
+            fun buildMethod(name : String) = MethodSpec.methodBuilder(name).apply {
+                addAnnotation(TypeConverter::class.java)
+                addModifiers(Modifier.PUBLIC)
+                returns(to)
+                addParameter(ParameterSpec.builder(from, "input").build())
+                if (to.isPrimitive) {
+                    addStatement("return 0")
+                } else {
+                    addStatement("return null")
+                }
+            }.build()
+            addMethod(buildMethod("convertF"))
+            if (duplicate) {
+                addMethod(buildMethod("convertF2"))
+            }
+        }.build().toString()
+        return JavaFileObjects.forSourceString(CONVERTER.toString(),
+                "package ${CONVERTER.packageName()};\n$code")
+    }
+
+    private fun singleClass(vararg jfo: JavaFileObject,
+                            handler: (CustomTypeConverter?, TestInvocation) -> Unit)
+            : CompileTester {
+        return simpleRun(*((jfo.toList() + CONTAINER).toTypedArray())) { invocation ->
+            val processed = CustomConverterProcessor.findConverters(invocation.context,
+                    invocation.processingEnv.elementUtils.getTypeElement("foo.bar.Container"))
+            handler(processed.converters.firstOrNull()?.custom, invocation)
+        }
+    }
+}
diff --git a/room/compiler/src/test/kotlin/android/arch/persistence/room/processor/DaoProcessorTest.kt b/room/compiler/src/test/kotlin/android/arch/persistence/room/processor/DaoProcessorTest.kt
new file mode 100644
index 0000000..a53cc1e
--- /dev/null
+++ b/room/compiler/src/test/kotlin/android/arch/persistence/room/processor/DaoProcessorTest.kt
@@ -0,0 +1,227 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.arch.persistence.room.processor
+
+import COMMON
+import android.arch.persistence.room.ext.RoomTypeNames
+import android.arch.persistence.room.testing.TestInvocation
+import android.arch.persistence.room.testing.TestProcessor
+import android.arch.persistence.room.vo.Dao
+import android.arch.persistence.room.vo.Warning
+import com.google.auto.common.MoreElements
+import com.google.auto.common.MoreTypes
+import com.google.common.truth.Truth
+import com.google.testing.compile.CompileTester
+import com.google.testing.compile.JavaFileObjects
+import com.google.testing.compile.JavaSourcesSubjectFactory
+import createVerifierFromEntities
+import org.hamcrest.CoreMatchers.`is`
+import org.hamcrest.MatcherAssert.assertThat
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.junit.runners.Parameterized
+
+@RunWith(Parameterized::class)
+class DaoProcessorTest(val enableVerification : Boolean) {
+    companion object {
+        const val DAO_PREFIX = """
+            package foo.bar;
+            import android.arch.persistence.room.*;
+            """
+        @Parameterized.Parameters(name = "enableDbVerification={0}")
+        @JvmStatic
+        fun getParams() = arrayOf(true, false)
+    }
+
+    @Test
+    fun testNonAbstract() {
+        singleDao("@Dao public class MyDao {}") { dao, invocation -> }
+                .failsToCompile()
+                .withErrorContaining(ProcessorErrors.DAO_MUST_BE_AN_ABSTRACT_CLASS_OR_AN_INTERFACE)
+    }
+
+    @Test
+    fun testAbstractMethodWithoutQuery() {
+        singleDao("""
+                @Dao public interface MyDao {
+                    int getFoo();
+                }
+        """) { dao, invocation ->
+        }.failsToCompile()
+                .withErrorContaining(ProcessorErrors.ABSTRACT_METHOD_IN_DAO_MISSING_ANY_ANNOTATION)
+    }
+
+    @Test
+    fun testBothAnnotations() {
+        singleDao("""
+                @Dao public interface MyDao {
+                    @Query("select 1")
+                    @Insert
+                    int getFoo(int x);
+                }
+        """) { dao, invocation ->
+        }.failsToCompile().withErrorContaining(
+                ProcessorErrors.CANNOT_USE_MORE_THAN_ONE_DAO_METHOD_ANNOTATION)
+    }
+
+    @Test
+    fun testAbstractClass() {
+        singleDao("""
+                @Dao abstract class MyDao {
+                    @Query("SELECT uid FROM User")
+                    abstract int[] getIds();
+                }
+                """) { dao, invocation ->
+            assertThat(dao.queryMethods.size, `is`(1))
+            val method = dao.queryMethods.first()
+            assertThat(method.name, `is`("getIds"))
+        }.compilesWithoutError()
+    }
+
+    @Test
+    fun testInterface() {
+        singleDao("""
+                @Dao interface MyDao {
+                    @Query("SELECT uid FROM User")
+                    abstract int[] getIds();
+                }
+                """) { dao, invocation ->
+            assertThat(dao.queryMethods.size, `is`(1))
+            val method = dao.queryMethods.first()
+            assertThat(method.name, `is`("getIds"))
+        }.compilesWithoutError()
+    }
+
+    @Test
+    fun testWithInsertAndQuery() {
+        singleDao("""
+                @Dao abstract class MyDao {
+                    @Query("SELECT uid FROM User")
+                    abstract int[] getIds();
+                    @Insert
+                    abstract void insert(User user);
+                }
+                """) { dao, invocation ->
+            assertThat(dao.queryMethods.size, `is`(1))
+            val method = dao.queryMethods.first()
+            assertThat(method.name, `is`("getIds"))
+            assertThat(dao.insertionMethods.size, `is`(1))
+            val insertMethod = dao.insertionMethods.first()
+            assertThat(insertMethod.name, `is`("insert"))
+        }.compilesWithoutError()
+    }
+
+    @Test
+    fun skipQueryVerification() {
+        singleDao("""
+                @Dao @SkipQueryVerification interface MyDao {
+                    @Query("SELECT nonExistingField FROM User")
+                    abstract int[] getIds();
+                }
+                """) { dao, invocation ->
+            assertThat(dao.queryMethods.size, `is`(1))
+            val method = dao.queryMethods.first()
+            assertThat(method.name, `is`("getIds"))
+        }.compilesWithoutError()
+    }
+
+    @Test
+    fun suppressedWarnings() {
+        singleDao("""
+            @SuppressWarnings({"ALL", RoomWarnings.CURSOR_MISMATCH})
+            @Dao interface MyDao {
+                @Query("SELECT * from user")
+                abstract User users();
+            }
+            """) { dao, invocation ->
+            val dbType = MoreTypes.asDeclared(invocation.context.processingEnv.elementUtils
+                    .getTypeElement(RoomTypeNames.ROOM_DB.toString()).asType())
+            val daoProcessor = DaoProcessor(invocation.context, dao.element, dbType, null)
+            assertThat(daoProcessor.context.logger
+                    .suppressedWarnings, `is`(setOf(Warning.ALL, Warning.CURSOR_MISMATCH)))
+
+            dao.queryMethods.forEach {
+                assertThat(QueryMethodProcessor(
+                        baseContext = daoProcessor.context,
+                        containing = MoreTypes.asDeclared(dao.element.asType()),
+                        executableElement = it.element,
+                        dbVerifier = null).context.logger.suppressedWarnings,
+                        `is`(setOf(Warning.ALL, Warning.CURSOR_MISMATCH)))
+            }
+        }.compilesWithoutError()
+    }
+
+    @Test
+    fun suppressedWarningsInheritance() {
+        singleDao("""
+            @SuppressWarnings(RoomWarnings.CURSOR_MISMATCH)
+            @Dao interface MyDao {
+                @SuppressWarnings("ALL")
+                @Query("SELECT * from user")
+                abstract User users();
+            }
+            """) { dao, invocation ->
+            val dbType = MoreTypes.asDeclared(invocation.context.processingEnv.elementUtils
+                    .getTypeElement(RoomTypeNames.ROOM_DB.toString()).asType())
+            val daoProcessor = DaoProcessor(invocation.context, dao.element, dbType, null)
+            assertThat(daoProcessor.context.logger
+                    .suppressedWarnings, `is`(setOf(Warning.CURSOR_MISMATCH)))
+
+            dao.queryMethods.forEach {
+                assertThat(QueryMethodProcessor(
+                        baseContext = daoProcessor.context,
+                        containing = MoreTypes.asDeclared(dao.element.asType()),
+                        executableElement = it.element,
+                        dbVerifier = null).context.logger.suppressedWarnings,
+                        `is`(setOf(Warning.ALL, Warning.CURSOR_MISMATCH)))
+            }
+        }.compilesWithoutError()
+    }
+
+    fun singleDao(vararg inputs: String, handler: (Dao, TestInvocation) -> Unit):
+            CompileTester {
+        return Truth.assertAbout(JavaSourcesSubjectFactory.javaSources())
+                .that(listOf(JavaFileObjects.forSourceString("foo.bar.MyDao",
+                        DAO_PREFIX + inputs.joinToString("\n")
+                ), COMMON.USER))
+                .processedWith(TestProcessor.builder()
+                        .forAnnotations(android.arch.persistence.room.Dao::class,
+                                android.arch.persistence.room.Entity::class)
+                        .nextRunHandler { invocation ->
+                            val dao = invocation.roundEnv
+                                    .getElementsAnnotatedWith(
+                                            android.arch.persistence.room.Dao::class.java)
+                                    .first()
+                            val dbVerifier = if (enableVerification) {
+                                createVerifierFromEntities(invocation)
+                            } else {
+                                null
+                            }
+                            val dbType = MoreTypes.asDeclared(
+                                    invocation.context.processingEnv.elementUtils
+                                            .getTypeElement(RoomTypeNames.ROOM_DB.toString())
+                                            .asType())
+                            val parser = DaoProcessor(invocation.context,
+                                    MoreElements.asType(dao), dbType, dbVerifier)
+
+                            val parsedDao = parser.process()
+                            handler(parsedDao, invocation)
+                            true
+                        }
+                        .build())
+    }
+}
diff --git a/room/compiler/src/test/kotlin/android/arch/persistence/room/processor/DatabaseProcessorTest.kt b/room/compiler/src/test/kotlin/android/arch/persistence/room/processor/DatabaseProcessorTest.kt
new file mode 100644
index 0000000..56e6641
--- /dev/null
+++ b/room/compiler/src/test/kotlin/android/arch/persistence/room/processor/DatabaseProcessorTest.kt
@@ -0,0 +1,717 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.arch.persistence.room.processor
+
+import android.arch.persistence.room.RoomProcessor
+import android.arch.persistence.room.solver.query.result.EntityRowAdapter
+import android.arch.persistence.room.solver.query.result.PojoRowAdapter
+import android.arch.persistence.room.testing.TestInvocation
+import android.arch.persistence.room.testing.TestProcessor
+import android.arch.persistence.room.vo.Database
+import android.arch.persistence.room.vo.Warning
+import com.google.auto.common.MoreElements
+import com.google.common.truth.Truth
+import com.google.testing.compile.CompileTester
+import com.google.testing.compile.JavaFileObjects
+import com.google.testing.compile.JavaSourcesSubjectFactory
+import com.squareup.javapoet.ClassName
+import org.hamcrest.CoreMatchers.`is`
+import org.hamcrest.CoreMatchers.instanceOf
+import org.hamcrest.CoreMatchers.not
+import org.hamcrest.CoreMatchers.notNullValue
+import org.hamcrest.CoreMatchers.sameInstance
+import org.hamcrest.MatcherAssert.assertThat
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.junit.runners.JUnit4
+import javax.tools.JavaFileObject
+import javax.tools.StandardLocation
+
+@RunWith(JUnit4::class)
+class DatabaseProcessorTest {
+    companion object {
+        const val DATABASE_PREFIX = """
+            package foo.bar;
+            import android.arch.persistence.room.*;
+            """
+        val DB1: JavaFileObject = JavaFileObjects.forSourceString("foo.bar.Db1",
+                """
+                $DATABASE_PREFIX
+                @Database(entities = {Book.class}, version = 42)
+                public abstract class Db1 extends RoomDatabase {
+                    abstract BookDao bookDao();
+                }
+                """)
+        val DB2: JavaFileObject = JavaFileObjects.forSourceString("foo.bar.Db2",
+                """
+                $DATABASE_PREFIX
+                @Database(entities = {Book.class}, version = 42)
+                public abstract class Db2 extends RoomDatabase {
+                    abstract BookDao bookDao();
+                }
+                """)
+        val DB3: JavaFileObject = JavaFileObjects.forSourceString("foo.bar.Db3",
+                """
+                $DATABASE_PREFIX
+                @Database(entities = {Book.class}, version = 42)
+                public abstract class Db3 extends RoomDatabase {
+                }
+                """)
+        val USER: JavaFileObject = JavaFileObjects.forSourceString("foo.bar.User",
+                """
+                package foo.bar;
+                import android.arch.persistence.room.*;
+                @Entity
+                public class User {
+                    @PrimaryKey
+                    int uid;
+                }
+                """)
+        val USER_DAO: JavaFileObject = JavaFileObjects.forSourceString("foo.bar.UserDao",
+                """
+                package foo.bar;
+                import android.arch.persistence.room.*;
+                @Dao
+                public interface UserDao {
+                    @Query("SELECT * FROM user")
+                    public java.util.List<User> loadAll();
+
+                    @Insert
+                    public void insert(User... users);
+
+                    @Query("SELECT * FROM user where uid = :uid")
+                    public User loadOne(int uid);
+
+                    @TypeConverters(Converter.class)
+                    @Query("SELECT * FROM user where uid = :uid")
+                    public User loadWithConverter(int uid);
+
+                    @Query("SELECT * FROM user where uid = :uid")
+                    public Pojo loadOnePojo(int uid);
+
+                    @Query("SELECT * FROM user")
+                    public java.util.List<Pojo> loadAllPojos();
+
+                    @TypeConverters(Converter.class)
+                    @Query("SELECT * FROM user where uid = :uid")
+                    public Pojo loadPojoWithConverter(int uid);
+
+                    public static class Converter {
+                        @TypeConverter
+                        public static java.util.Date foo(Long input) {return null;}
+                    }
+
+                    public static class Pojo {
+                        public int uid;
+                    }
+                }
+                """)
+        val BOOK: JavaFileObject = JavaFileObjects.forSourceString("foo.bar.Book",
+                """
+                package foo.bar;
+                import android.arch.persistence.room.*;
+                @Entity
+                public class Book {
+                    @PrimaryKey
+                    int bookId;
+                }
+                """)
+        val BOOK_DAO: JavaFileObject = JavaFileObjects.forSourceString("foo.bar.BookDao",
+                """
+                package foo.bar;
+                import android.arch.persistence.room.*;
+                @Dao
+                public interface BookDao {
+                    @Query("SELECT * FROM book")
+                    public java.util.List<Book> loadAllBooks();
+                    @Insert
+                    public void insert(Book book);
+                }
+                """)
+    }
+
+    @Test
+    fun simple() {
+        singleDb("""
+            @Database(entities = {User.class}, version = 42)
+            public abstract class MyDb extends RoomDatabase {
+                abstract UserDao userDao();
+            }
+            """, USER, USER_DAO) { db, invocation ->
+            assertThat(db.daoMethods.size, `is`(1))
+            assertThat(db.entities.size, `is`(1))
+        }.compilesWithoutError()
+    }
+
+    @Test
+    fun multiple() {
+        singleDb("""
+            @Database(entities = {User.class, Book.class}, version = 42)
+            public abstract class MyDb extends RoomDatabase {
+                abstract UserDao userDao();
+                abstract BookDao bookDao();
+            }
+            """, USER, USER_DAO, BOOK, BOOK_DAO) { db, invocation ->
+            assertThat(db.daoMethods.size, `is`(2))
+            assertThat(db.entities.size, `is`(2))
+            assertThat(db.daoMethods.map { it.name }, `is`(listOf("userDao", "bookDao")))
+            assertThat(db.entities.map { it.type.toString() },
+                    `is`(listOf("foo.bar.User", "foo.bar.Book")))
+        }.compilesWithoutError()
+    }
+
+    @Test
+    fun detectMissingBaseClass() {
+        singleDb("""
+            @Database(entities = {User.class, Book.class}, version = 42)
+            public abstract class MyDb {
+            }
+            """, USER, BOOK) { db, invocation ->
+        }.failsToCompile().withErrorContaining(ProcessorErrors.DB_MUST_EXTEND_ROOM_DB)
+    }
+
+    @Test
+    fun detectMissingTable() {
+        singleDb(
+                """
+                @Database(entities = {Book.class}, version = 42)
+                public abstract class MyDb extends RoomDatabase {
+                    abstract BookDao bookDao();
+                }
+                """, BOOK, JavaFileObjects.forSourceString("foo.bar.BookDao",
+                """
+                package foo.bar;
+                import android.arch.persistence.room.*;
+                @Dao
+                public interface BookDao {
+                    @Query("SELECT * FROM nonExistentTable")
+                    public java.util.List<Book> loadAllBooks();
+                }
+                """)){ db, invocation ->
+
+        }.failsToCompile().withErrorContaining("no such table: nonExistentTable")
+    }
+
+    @Test
+    fun detectDuplicateTableNames() {
+        singleDb("""
+                @Database(entities = {User.class, AnotherClass.class}, version = 42)
+                public abstract class MyDb extends RoomDatabase {
+                    abstract UserDao userDao();
+                }
+                """, USER, USER_DAO, JavaFileObjects.forSourceString("foo.bar.AnotherClass",
+                """
+                package foo.bar;
+                import android.arch.persistence.room.*;
+                @Entity(tableName="user")
+                public class AnotherClass {
+                    @PrimaryKey
+                    int uid;
+                }
+                """)) { db, invocation ->
+        }.failsToCompile().withErrorContaining(
+                ProcessorErrors.duplicateTableNames("user",
+                        listOf("foo.bar.User", "foo.bar.AnotherClass"))
+        )
+    }
+
+    @Test
+    fun skipBadQueryVerification() {
+        singleDb(
+                """
+                @SkipQueryVerification
+                @Database(entities = {Book.class}, version = 42)
+                public abstract class MyDb extends RoomDatabase {
+                    abstract BookDao bookDao();
+                }
+                """, BOOK, JavaFileObjects.forSourceString("foo.bar.BookDao",
+                """
+                package foo.bar;
+                import android.arch.persistence.room.*;
+                @Dao
+                public interface BookDao {
+                    @Query("SELECT nonExistingField FROM Book")
+                    public java.util.List<Book> loadAllBooks();
+                }
+                """)){ db, invocation ->
+
+        }.compilesWithoutError()
+    }
+
+    @Test
+    fun multipleDatabases() {
+        val db1_2 = JavaFileObjects.forSourceString("foo.barx.Db1",
+                """
+                package foo.barx;
+                import android.arch.persistence.room.*;
+                import foo.bar.*;
+                @Database(entities = {Book.class}, version = 42)
+                public abstract class Db1 extends RoomDatabase {
+                    abstract BookDao bookDao();
+                }
+                """)
+        Truth.assertAbout(JavaSourcesSubjectFactory.javaSources())
+                .that(listOf(BOOK, BOOK_DAO, DB1, DB2, db1_2))
+                .processedWith(RoomProcessor())
+                .compilesWithoutError()
+                .and()
+                .generatesFileNamed(StandardLocation.CLASS_OUTPUT, "foo.bar", "Db1_Impl.class")
+                .and()
+                .generatesFileNamed(StandardLocation.CLASS_OUTPUT, "foo.bar", "Db2_Impl.class")
+                .and()
+                .generatesFileNamed(StandardLocation.CLASS_OUTPUT, "foo.barx", "Db1_Impl.class")
+                .and()
+                .generatesFileNamed(StandardLocation.CLASS_OUTPUT, "foo.bar",
+                        "BookDao_Db1_0_Impl.class")
+                .and()
+                .generatesFileNamed(StandardLocation.CLASS_OUTPUT, "foo.bar",
+                        "BookDao_Db1_1_Impl.class")
+                .and()
+                .generatesFileNamed(StandardLocation.CLASS_OUTPUT, "foo.bar",
+                        "BookDao_Db2_Impl.class")
+    }
+
+    @Test
+    fun twoDaoMethodsForTheSameDao() {
+        singleDb(
+                """
+                @Database(entities = {User.class}, version = 42)
+                public abstract class MyDb extends RoomDatabase {
+                    abstract UserDao userDao();
+                    abstract UserDao userDao2();
+                }
+                """, USER, USER_DAO){db, invocation -> }
+                .failsToCompile()
+                .withErrorContaining(ProcessorErrors.DAO_METHOD_CONFLICTS_WITH_OTHERS)
+                .and()
+                .withErrorContaining(ProcessorErrors.duplicateDao(
+                        ClassName.get("foo.bar", "UserDao"), listOf("userDao", "userDao2")
+                ))
+    }
+
+    @Test
+    fun suppressedWarnings() {
+        singleDb(
+                """
+                @SuppressWarnings(RoomWarnings.CURSOR_MISMATCH)
+                @Database(entities = {User.class}, version = 42)
+                public abstract class MyDb extends RoomDatabase {
+                    abstract UserDao userDao();
+                }
+                """, USER, USER_DAO) {db, invocation ->
+            assertThat(DatabaseProcessor(invocation.context, db.element)
+                    .context.logger.suppressedWarnings, `is`(setOf(Warning.CURSOR_MISMATCH)))
+        }.compilesWithoutError()
+    }
+
+    @Test
+    fun duplicateIndexNames() {
+        val entity1 = JavaFileObjects.forSourceString("foo.bar.Entity1",
+                """
+                package foo.bar;
+                import android.arch.persistence.room.*;
+                @Entity(indices = {@Index(name ="index_name", value = {"name"})})
+                public class Entity1 {
+                    @PrimaryKey
+                    int uid;
+                    String name;
+                }
+                """)
+
+        val entity2 = JavaFileObjects.forSourceString("foo.bar.Entity2",
+                """
+                package foo.bar;
+                import android.arch.persistence.room.*;
+                @Entity(indices = {@Index(name ="index_name", value = {"anotherName"})})
+                public class Entity2 {
+                    @PrimaryKey
+                    int uid;
+                    String anotherName;
+                }
+                """)
+        singleDb("""
+                @Database(entities = {Entity1.class, Entity2.class}, version = 42)
+                public abstract class MyDb extends RoomDatabase {
+                }
+                """, entity1, entity2){ db, invocation ->
+
+        }.failsToCompile().withErrorContaining(
+                ProcessorErrors.duplicateIndexInDatabase("index_name",
+                        listOf("foo.bar.Entity1 > index_name", "foo.bar.Entity2 > index_name"))
+        )
+    }
+
+    @Test
+    fun foreignKey_missingParent() {
+        val entity1 = JavaFileObjects.forSourceString("foo.bar.Entity1",
+                """
+                package foo.bar;
+                import android.arch.persistence.room.*;
+                @Entity(foreignKeys = @ForeignKey(entity = ${COMMON.USER_TYPE_NAME}.class,
+                        parentColumns = "lastName",
+                        childColumns = "name"))
+                public class Entity1 {
+                    @PrimaryKey
+                    int uid;
+                    String name;
+                }
+                """)
+        singleDb("""
+                @Database(entities = {Entity1.class}, version = 42)
+                public abstract class MyDb extends RoomDatabase {
+                }
+                """, entity1, COMMON.USER){ db, invocation ->
+
+        }.failsToCompile().withErrorContaining(
+                ProcessorErrors.foreignKeyMissingParentEntityInDatabase("User", "foo.bar.Entity1")
+        )
+    }
+
+    @Test
+    fun foreignKey_missingParentIndex() {
+        val entity1 = JavaFileObjects.forSourceString("foo.bar.Entity1",
+                """
+                package foo.bar;
+                import android.arch.persistence.room.*;
+                @Entity(foreignKeys = @ForeignKey(entity = ${COMMON.USER_TYPE_NAME}.class,
+                        parentColumns = "lastName",
+                        childColumns = "name"))
+                public class Entity1 {
+                    @PrimaryKey
+                    int uid;
+                    String name;
+                }
+                """)
+        singleDb("""
+                @Database(entities = {Entity1.class, User.class}, version = 42)
+                public abstract class MyDb extends RoomDatabase {
+                }
+                """, entity1, COMMON.USER){ db, invocation ->
+
+        }.failsToCompile().withErrorContaining(
+                ProcessorErrors.foreignKeyMissingIndexInParent(
+                        parentEntity = COMMON.USER_TYPE_NAME.toString(),
+                        parentColumns = listOf("lastName"),
+                        childEntity = "foo.bar.Entity1",
+                        childColumns = listOf("name")
+                )
+        )
+    }
+
+    @Test
+    fun foreignKey_goodWithPrimaryKey() {
+        val entity1 = JavaFileObjects.forSourceString("foo.bar.Entity1",
+                """
+                package foo.bar;
+                import android.arch.persistence.room.*;
+                @Entity(foreignKeys = @ForeignKey(entity = Entity2.class,
+                    parentColumns = "uid",
+                    childColumns = "parentId"))
+                public class Entity1 {
+                    @PrimaryKey
+                    int uid;
+                    int parentId;
+                    String name;
+                }
+                """)
+
+        val entity2 = JavaFileObjects.forSourceString("foo.bar.Entity2",
+                """
+                package foo.bar;
+                import android.arch.persistence.room.*;
+                @Entity
+                public class Entity2 {
+                    @PrimaryKey
+                    int uid;
+                    String anotherName;
+                }
+                """)
+        singleDb("""
+                @Database(entities = {Entity1.class, Entity2.class}, version = 42)
+                public abstract class MyDb extends RoomDatabase {
+                }
+                """, entity1, entity2){ db, invocation ->
+
+        }.compilesWithoutError()
+    }
+
+    @Test
+    fun foreignKey_missingParentColumn() {
+        val entity1 = JavaFileObjects.forSourceString("foo.bar.Entity1",
+                """
+                package foo.bar;
+                import android.arch.persistence.room.*;
+                @Entity(foreignKeys = @ForeignKey(entity = Entity2.class,
+                    parentColumns = {"anotherName", "anotherName2"},
+                    childColumns = {"name", "name2"}))
+                public class Entity1 {
+                    @PrimaryKey
+                    int uid;
+                    String name;
+                    String name2;
+                }
+                """)
+
+        val entity2 = JavaFileObjects.forSourceString("foo.bar.Entity2",
+                """
+                package foo.bar;
+                import android.arch.persistence.room.*;
+                @Entity
+                public class Entity2 {
+                    @PrimaryKey
+                    int uid;
+                    String anotherName;
+                }
+                """)
+        singleDb("""
+                @Database(entities = {Entity1.class, Entity2.class}, version = 42)
+                public abstract class MyDb extends RoomDatabase {
+                }
+                """, entity1, entity2){ db, invocation ->
+        }.failsToCompile().withErrorContaining(
+                ProcessorErrors.foreignKeyParentColumnDoesNotExist("foo.bar.Entity2",
+                        "anotherName2", listOf("uid", "anotherName"))
+        )
+    }
+
+    @Test
+    fun foreignKey_goodWithIndex() {
+        val entity1 = JavaFileObjects.forSourceString("foo.bar.Entity1",
+                """
+                package foo.bar;
+                import android.arch.persistence.room.*;
+                @Entity(foreignKeys = @ForeignKey(entity = Entity2.class,
+                    parentColumns = {"anotherName", "anotherName2"},
+                    childColumns = {"name", "name2"}))
+                public class Entity1 {
+                    @PrimaryKey
+                    int uid;
+                    String name;
+                    String name2;
+                }
+                """)
+
+        val entity2 = JavaFileObjects.forSourceString("foo.bar.Entity2",
+                """
+                package foo.bar;
+                import android.arch.persistence.room.*;
+                @Entity(indices = @Index(value = {"anotherName2", "anotherName"}, unique = true))
+                public class Entity2 {
+                    @PrimaryKey
+                    int uid;
+                    String anotherName;
+                    String anotherName2;
+                }
+                """)
+        singleDb("""
+                @Database(entities = {Entity1.class, Entity2.class}, version = 42)
+                public abstract class MyDb extends RoomDatabase {
+                }
+                """, entity1, entity2){ db, invocation ->
+        }.compilesWithoutError()
+    }
+
+    @Test
+    fun insertNotAReferencedEntity() {
+        singleDb("""
+                @Database(entities = {User.class}, version = 42)
+                public abstract class MyDb extends RoomDatabase {
+                    abstract BookDao bookDao();
+                }
+                """, USER, USER_DAO, BOOK, BOOK_DAO){ db, invocation ->
+        }.failsToCompile().withErrorContaining(
+                ProcessorErrors.shortcutEntityIsNotInDatabase(
+                        database = "foo.bar.MyDb",
+                        dao = "foo.bar.BookDao",
+                        entity = "foo.bar.Book"
+                )
+        )
+    }
+
+    @Test
+    fun cache_entity() {
+        singleDb("""
+                @Database(entities = {User.class}, version = 42)
+                @SkipQueryVerification
+                public abstract class MyDb extends RoomDatabase {
+                    public abstract MyUserDao userDao();
+                    @Dao
+                    interface MyUserDao {
+                        @Insert
+                        public void insert(User... users);
+
+                        @Query("SELECT * FROM user where uid = :uid")
+                        public User loadOne(int uid);
+
+                        @TypeConverters(Converter.class)
+                        @Query("SELECT * FROM user where uid = :uid")
+                        public User loadWithConverter(int uid);
+                    }
+                    public static class Converter {
+                        @TypeConverter
+                        public static java.util.Date foo(Long input) {return null;}
+                    }
+                }
+                """, USER, USER_DAO){ db, invocation ->
+            val userDao = db.daoMethods.first().dao
+            val insertionMethod = userDao.insertionMethods.find { it.name == "insert" }
+            assertThat(insertionMethod, notNullValue())
+            val loadOne = userDao.queryMethods.find { it.name == "loadOne" }
+            assertThat(loadOne, notNullValue())
+            val adapter = loadOne?.queryResultBinder?.adapter?.rowAdapter
+            assertThat("test sanity", adapter, instanceOf(EntityRowAdapter::class.java))
+            val adapterEntity = (adapter as EntityRowAdapter).entity
+            assertThat(insertionMethod?.entities?.values?.first(), sameInstance(adapterEntity))
+
+            val withConverter = userDao.queryMethods.find { it.name == "loadWithConverter" }
+            assertThat(withConverter, notNullValue())
+            val convAdapter = withConverter?.queryResultBinder?.adapter?.rowAdapter
+            assertThat("test sanity", adapter, instanceOf(EntityRowAdapter::class.java))
+            val convAdapterEntity = (convAdapter as EntityRowAdapter).entity
+            assertThat(insertionMethod?.entities?.values?.first(),
+                    not(sameInstance(convAdapterEntity)))
+
+            assertThat(convAdapterEntity, notNullValue())
+            assertThat(adapterEntity, notNullValue())
+        }.compilesWithoutError()
+    }
+
+    @Test
+    fun cache_pojo() {
+        singleDb("""
+                @Database(entities = {User.class}, version = 42)
+                public abstract class MyDb extends RoomDatabase {
+                    public abstract UserDao userDao();
+                }
+                """, USER, USER_DAO){ db, invocation ->
+            val userDao = db.daoMethods.first().dao
+            val loadOne = userDao.queryMethods.find { it.name == "loadOnePojo" }
+            assertThat(loadOne, notNullValue())
+            val adapter = loadOne?.queryResultBinder?.adapter?.rowAdapter
+            assertThat("test sanity", adapter, instanceOf(PojoRowAdapter::class.java))
+            val adapterPojo = (adapter as PojoRowAdapter).pojo
+
+            val loadAll = userDao.queryMethods.find { it.name == "loadAllPojos" }
+            assertThat(loadAll, notNullValue())
+            val loadAllAdapter = loadAll?.queryResultBinder?.adapter?.rowAdapter
+            assertThat("test sanity", loadAllAdapter, instanceOf(PojoRowAdapter::class.java))
+            val loadAllPojo = (loadAllAdapter as PojoRowAdapter).pojo
+            assertThat(adapter, not(sameInstance(loadAllAdapter)))
+            assertThat(adapterPojo, sameInstance(loadAllPojo))
+
+            val withConverter = userDao.queryMethods.find { it.name == "loadPojoWithConverter" }
+            assertThat(withConverter, notNullValue())
+            val convAdapter = withConverter?.queryResultBinder?.adapter?.rowAdapter
+            assertThat("test sanity", adapter, instanceOf(PojoRowAdapter::class.java))
+            val convAdapterPojo = (convAdapter as PojoRowAdapter).pojo
+            assertThat(convAdapterPojo, notNullValue())
+            assertThat(convAdapterPojo, not(sameInstance(adapterPojo)))
+        }.compilesWithoutError()
+    }
+
+    @Test
+    fun daoConstructor_RoomDatabase() {
+        assertConstructor(listOf(DB1), "BookDao(RoomDatabase db) {}")
+                .compilesWithoutError()
+    }
+
+    @Test
+    fun daoConstructor_specificDatabase() {
+        assertConstructor(listOf(DB1), "BookDao(Db1 db) {}")
+                .compilesWithoutError()
+    }
+
+    @Test
+    fun daoConstructor_wrongDatabase() {
+        assertConstructor(listOf(DB1, DB3), "BookDao(Db3 db) {}")
+                .failsToCompile()
+                .withErrorContaining(ProcessorErrors
+                        .daoMustHaveMatchingConstructor("foo.bar.BookDao", "foo.bar.Db1"))
+    }
+
+    @Test
+    fun daoConstructor_multipleDatabases_RoomDatabase() {
+        assertConstructor(listOf(DB1, DB2), "BookDao(RoomDatabase db) {}")
+                .compilesWithoutError()
+    }
+
+    @Test
+    fun daoConstructor_multipleDatabases_specificDatabases() {
+        assertConstructor(listOf(DB1, DB2), """
+                    BookDao(Db1 db) {}
+                    BookDao(Db2 db) {}
+                """)
+                .compilesWithoutError()
+    }
+
+    @Test
+    fun daoConstructor_multipleDatabases_empty() {
+        assertConstructor(listOf(DB1, DB2), """
+                    BookDao(Db1 db) {}
+                    BookDao() {} // Db2 uses this
+                """)
+                .compilesWithoutError()
+    }
+
+    @Test
+    fun daoConstructor_multipleDatabases_noMatch() {
+        assertConstructor(listOf(DB1, DB2), """
+                    BookDao(Db1 db) {}
+                """)
+                .failsToCompile()
+                .withErrorContaining(ProcessorErrors
+                        .daoMustHaveMatchingConstructor("foo.bar.BookDao", "foo.bar.Db2"))
+    }
+
+    fun assertConstructor(dbs: List<JavaFileObject>, constructor: String): CompileTester {
+        val bookDao = JavaFileObjects.forSourceString("foo.bar.BookDao",
+                """
+                package foo.bar;
+                import android.arch.persistence.room.*;
+                @Dao
+                public abstract class BookDao {
+                    $constructor
+                }
+                """)
+        return Truth.assertAbout(JavaSourcesSubjectFactory.javaSources())
+                .that(listOf(BOOK, bookDao) + dbs)
+                .processedWith(RoomProcessor())
+    }
+
+    fun singleDb(input: String, vararg otherFiles: JavaFileObject,
+                 handler: (Database, TestInvocation) -> Unit): CompileTester {
+        return Truth.assertAbout(JavaSourcesSubjectFactory.javaSources())
+                .that(otherFiles.toMutableList()
+                        + JavaFileObjects.forSourceString("foo.bar.MyDb",
+                        DATABASE_PREFIX + input
+                ))
+                .processedWith(TestProcessor.builder()
+                        .forAnnotations(android.arch.persistence.room.Database::class)
+                        .nextRunHandler { invocation ->
+                            val entity = invocation.roundEnv
+                                    .getElementsAnnotatedWith(
+                                            android.arch.persistence.room.Database::class.java)
+                                    .first()
+                            val parser = DatabaseProcessor(invocation.context,
+                                    MoreElements.asType(entity))
+                            val parsedDb = parser.process()
+                            handler(parsedDb, invocation)
+                            true
+                        }
+                        .build())
+    }
+}
diff --git a/room/compiler/src/test/kotlin/android/arch/persistence/room/processor/DeletionMethodProcessorTest.kt b/room/compiler/src/test/kotlin/android/arch/persistence/room/processor/DeletionMethodProcessorTest.kt
new file mode 100644
index 0000000..864ea9b
--- /dev/null
+++ b/room/compiler/src/test/kotlin/android/arch/persistence/room/processor/DeletionMethodProcessorTest.kt
@@ -0,0 +1,40 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package android.arch.persistence.room.processor
+
+import android.arch.persistence.room.Delete
+import android.arch.persistence.room.processor.ProcessorErrors.DELETION_MISSING_PARAMS
+import android.arch.persistence.room.processor.ProcessorErrors
+        .DELETION_METHODS_MUST_RETURN_VOID_OR_INT
+import android.arch.persistence.room.vo.DeletionMethod
+import org.junit.runner.RunWith
+import org.junit.runners.JUnit4
+import javax.lang.model.element.ExecutableElement
+import javax.lang.model.type.DeclaredType
+
+@Suppress("PLATFORM_CLASS_MAPPED_TO_KOTLIN")
+@RunWith(JUnit4::class)
+class DeletionMethodProcessorTest : ShortcutMethodProcessorTest<DeletionMethod>(Delete::class) {
+    override fun invalidReturnTypeError(): String = DELETION_METHODS_MUST_RETURN_VOID_OR_INT
+
+    override fun noParamsError(): String = DELETION_MISSING_PARAMS
+
+    override fun process(baseContext: Context, containing: DeclaredType,
+                         executableElement: ExecutableElement): DeletionMethod {
+        return DeletionMethodProcessor(baseContext, containing, executableElement).process()
+    }
+
+}
diff --git a/room/compiler/src/test/kotlin/android/arch/persistence/room/processor/EntityNameMatchingVariationsTest.kt b/room/compiler/src/test/kotlin/android/arch/persistence/room/processor/EntityNameMatchingVariationsTest.kt
new file mode 100644
index 0000000..e6a2ff3
--- /dev/null
+++ b/room/compiler/src/test/kotlin/android/arch/persistence/room/processor/EntityNameMatchingVariationsTest.kt
@@ -0,0 +1,76 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.arch.persistence.room.processor
+
+import android.arch.persistence.room.parser.SQLTypeAffinity
+import android.arch.persistence.room.vo.CallType
+import android.arch.persistence.room.vo.Field
+import android.arch.persistence.room.vo.FieldGetter
+import android.arch.persistence.room.vo.FieldSetter
+import org.hamcrest.CoreMatchers.`is`
+import org.hamcrest.MatcherAssert.assertThat
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.junit.runners.Parameterized
+import javax.lang.model.type.TypeKind.INT
+
+@RunWith(Parameterized::class)
+class EntityNameMatchingVariationsTest(triple: Triple<String, String, String>) :
+        BaseEntityParserTest() {
+    val fieldName = triple.first
+    val getterName = triple.second
+    val setterName = triple.third
+
+    companion object {
+        @JvmStatic
+        @Parameterized.Parameters(name = "{0}")
+        fun params() : List<Triple<String, String, String>> {
+            val result = arrayListOf<Triple<String, String, String>>()
+            arrayListOf("x", "_x", "mX").forEach { field ->
+                arrayListOf("getX", "x").forEach { getter ->
+                    arrayListOf("setX", "x").forEach { setter ->
+                        result.add(Triple(field, getter, setter))
+                    }
+                }
+            }
+            return result
+        }
+    }
+
+    @Test
+    fun testSuccessfulParamToMethodMatching() {
+        singleEntity("""
+                @PrimaryKey
+                private int $fieldName;
+                public int $getterName() { return $fieldName; }
+                public void $setterName(int id) { this.$fieldName = id; }
+            """) { entity, invocation ->
+            assertThat(entity.type.toString(), `is`("foo.bar.MyEntity"))
+            assertThat(entity.fields.size, `is`(1))
+            val field = entity.fields.first()
+            val intType = invocation.processingEnv.typeUtils.getPrimitiveType(INT)
+            assertThat(field, `is`(Field(
+                    element = field.element,
+                    name = fieldName,
+                    type = intType,
+                    columnName = fieldName,
+                    affinity = SQLTypeAffinity.INTEGER)))
+            assertThat(field.setter, `is`(FieldSetter(setterName, intType, CallType.METHOD)))
+            assertThat(field.getter, `is`(FieldGetter(getterName, intType, CallType.METHOD)))
+        }.compilesWithoutError()
+    }
+}
diff --git a/room/compiler/src/test/kotlin/android/arch/persistence/room/processor/EntityProcessorTest.kt b/room/compiler/src/test/kotlin/android/arch/persistence/room/processor/EntityProcessorTest.kt
new file mode 100644
index 0000000..210f750
--- /dev/null
+++ b/room/compiler/src/test/kotlin/android/arch/persistence/room/processor/EntityProcessorTest.kt
@@ -0,0 +1,1411 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.arch.persistence.room.processor
+
+import COMMON
+import android.arch.persistence.room.parser.SQLTypeAffinity
+import android.arch.persistence.room.processor.ProcessorErrors.RELATION_IN_ENTITY
+import android.arch.persistence.room.vo.CallType
+import android.arch.persistence.room.vo.Field
+import android.arch.persistence.room.vo.FieldGetter
+import android.arch.persistence.room.vo.FieldSetter
+import android.arch.persistence.room.vo.Index
+import android.arch.persistence.room.vo.Pojo
+import com.google.testing.compile.JavaFileObjects
+import org.hamcrest.CoreMatchers.`is`
+import org.hamcrest.MatcherAssert.assertThat
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.junit.runners.JUnit4
+import javax.lang.model.type.TypeKind.INT
+
+@RunWith(JUnit4::class)
+class EntityProcessorTest : BaseEntityParserTest() {
+    @Test
+    fun simple() {
+        singleEntity("""
+                @PrimaryKey
+                private int id;
+                public int getId() { return id; }
+                public void setId(int id) { this.id = id; }
+            """) { entity, invocation ->
+            assertThat(entity.type.toString(), `is`("foo.bar.MyEntity"))
+            assertThat(entity.fields.size, `is`(1))
+            val field = entity.fields.first()
+            val intType = invocation.processingEnv.typeUtils.getPrimitiveType(INT)
+            assertThat(field, `is`(Field(
+                    element = field.element,
+                    name = "id",
+                    type = intType,
+                    columnName = "id",
+                    affinity = SQLTypeAffinity.INTEGER)))
+            assertThat(field.setter, `is`(FieldSetter("setId", intType, CallType.METHOD)))
+            assertThat(field.getter, `is`(FieldGetter("getId", intType, CallType.METHOD)))
+            assertThat(entity.primaryKey.fields, `is`(listOf(field)))
+        }.compilesWithoutError()
+    }
+
+    @Test
+    fun noGetter() {
+        singleEntity("""
+                @PrimaryKey
+                private int id;
+                public void setId(int id) {this.id = id;}
+                """) { entity, invocation -> }
+                .failsToCompile()
+                .withErrorContaining(ProcessorErrors.CANNOT_FIND_GETTER_FOR_FIELD)
+    }
+
+    @Test
+    fun noSetter() {
+        singleEntity("""
+                @PrimaryKey
+                private int id;
+                public int getId(){ return id; }
+                """) { entity, invocation -> }
+                .failsToCompile()
+                .withErrorContaining(ProcessorErrors.CANNOT_FIND_SETTER_FOR_FIELD)
+    }
+
+    @Test
+    fun tooManyGetters() {
+        singleEntity("""
+                @PrimaryKey
+                private int id;
+                public void setId(int id) {}
+                public int getId(){ return id; }
+                public int id(){ return id; }
+                """) { entity, invocation -> }
+                .failsToCompile()
+                .withErrorContaining("getId, id")
+    }
+
+    @Test
+    fun tooManyGettersWithIgnore() {
+        singleEntity("""
+                @PrimaryKey
+                private int id;
+                public void setId(int id) {}
+                public int getId(){ return id; }
+                @Ignore public int id(){ return id; }
+                """) { entity, invocation ->
+            assertThat(entity.fields.first().getter.name, `is`("getId"))
+        }.compilesWithoutError()
+    }
+
+    @Test
+    fun tooManyGettersWithDifferentVisibility() {
+        singleEntity("""
+                @PrimaryKey
+                private int id;
+                public void setId(int id) {}
+                public int getId(){ return id; }
+                protected int id(){ return id; }
+                """) { entity, invocation ->
+            assertThat(entity.fields.first().getter.name, `is`("getId"))
+        }.compilesWithoutError()
+    }
+
+    @Test
+    fun tooManyGettersWithDifferentTypes() {
+        singleEntity("""
+                @PrimaryKey
+                public int id;
+                public void setId(int id) {}
+                public int getId(){ return id; }
+                """) { entity, invocation ->
+            assertThat(entity.fields.first().getter.name, `is`("id"))
+            assertThat(entity.fields.first().getter.callType, `is`(CallType.FIELD))
+        }.compilesWithoutError()
+    }
+
+    @Test
+    fun tooManySetters() {
+        singleEntity("""
+                @PrimaryKey
+                private int id;
+                public void setId(int id) {}
+                public void id(int id) {}
+                public int getId(){ return id; }
+                """) { entity, invocation -> }
+                .failsToCompile()
+                .withErrorContaining("setId, id")
+    }
+
+    @Test
+    fun tooManySettersWithIgnore() {
+        singleEntity("""
+                @PrimaryKey
+                private int id;
+                public void setId(int id) {}
+                @Ignore public void id(int id) {}
+                public int getId(){ return id; }
+                """) { entity, invocation ->
+            assertThat(entity.fields.first().setter.name, `is`("setId"))
+        }.compilesWithoutError()
+    }
+
+    @Test
+    fun tooManySettersWithDifferentVisibility() {
+        singleEntity("""
+                @PrimaryKey
+                private int id;
+                public void setId(int id) {}
+                protected void id(int id) {}
+                public int getId(){ return id; }
+                """) { entity, invocation ->
+            assertThat(entity.fields.first().setter.name, `is`("setId"))
+        }.compilesWithoutError()
+    }
+
+    @Test
+    fun tooManySettersWithDifferentTypes() {
+        singleEntity("""
+                @PrimaryKey
+                public int id;
+                public void setId(int id) {}
+                public int getId(){ return id; }
+                """) { entity, invocation ->
+            assertThat(entity.fields.first().setter.name, `is`("id"))
+            assertThat(entity.fields.first().setter.callType, `is`(CallType.FIELD))
+        }.compilesWithoutError()
+    }
+
+    @Test
+    fun preferPublicOverProtected() {
+        singleEntity("""
+                @PrimaryKey
+                int id;
+                public void setId(int id) {}
+                public int getId(){ return id; }
+                """) { entity, invocation ->
+            assertThat(entity.fields.first().setter.name, `is`("setId"))
+            assertThat(entity.fields.first().getter.name, `is`("getId"))
+        }.compilesWithoutError()
+    }
+
+    @Test
+    fun customName() {
+        singleEntity("""
+                @PrimaryKey
+                int x;
+                """, hashMapOf(Pair("tableName", "\"foo_table\""))) { entity, invocation ->
+            assertThat(entity.tableName, `is`("foo_table"))
+        }.compilesWithoutError()
+    }
+
+    @Test
+    fun emptyCustomName() {
+        singleEntity("""
+                @PrimaryKey
+                int x;
+                """, hashMapOf(Pair("tableName", "\" \""))) { entity, invocation ->
+        }.failsToCompile().withErrorContaining(ProcessorErrors.ENTITY_TABLE_NAME_CANNOT_BE_EMPTY)
+    }
+
+    @Test
+    fun missingPrimaryKey() {
+        singleEntity("""
+                """) { entity, invocation ->
+        }.failsToCompile()
+                .withErrorContaining(ProcessorErrors.MISSING_PRIMARY_KEY)
+    }
+
+    @Test
+    fun missingColumnAdapter() {
+        singleEntity("""
+                @PrimaryKey
+                public java.util.Date myDate;
+                """) { entity, invocation ->
+
+        }.failsToCompile().withErrorContaining(ProcessorErrors.CANNOT_FIND_COLUMN_TYPE_ADAPTER)
+    }
+
+    @Test
+    fun dropSubPrimaryKey() {
+        singleEntity(
+                """
+                @PrimaryKey
+                int id;
+                @Embedded
+                Point myPoint;
+                static class Point {
+                    @PrimaryKey
+                    int x;
+                    int y;
+                }
+                """
+        ) { entity, invocation ->
+            assertThat(entity.primaryKey.fields.map { it.name }, `is`(listOf("id")))
+        }.compilesWithoutError()
+                .withWarningCount(1)
+                .withWarningContaining(ProcessorErrors.embeddedPrimaryKeyIsDropped(
+                        "foo.bar.MyEntity", "x"))
+    }
+
+    @Test
+    fun ignoreDropSubPrimaryKey() {
+        singleEntity(
+                """
+                @PrimaryKey
+                int id;
+                @Embedded
+                @SuppressWarnings(RoomWarnings.PRIMARY_KEY_FROM_EMBEDDED_IS_DROPPED)
+                Point myPoint;
+                static class Point {
+                    @PrimaryKey
+                    int x;
+                    int y;
+                }
+                """
+        ) { entity, invocation ->
+            assertThat(entity.primaryKey.fields.map { it.name }, `is`(listOf("id")))
+        }.compilesWithoutError().withWarningCount(0)
+    }
+
+    private fun fieldsByName(entity : Pojo, vararg fieldNames : String) : List<Field> {
+        return fieldNames
+                .map { name -> entity.fields.find { it.name == name } }
+                .filterNotNull()
+    }
+
+    @Test
+    fun index_simple() {
+        val annotation = mapOf(
+                "indices" to """@Index("foo")"""
+        )
+        singleEntity(
+                """
+                @PrimaryKey
+                public int id;
+                public String foo;
+                """
+                , annotation) { entity, invocation ->
+            assertThat(entity.indices, `is`(
+                    listOf(Index(name = "index_MyEntity_foo",
+                            unique = false,
+                            fields = fieldsByName(entity, "foo")))))
+        }.compilesWithoutError()
+    }
+
+    @Test
+    fun index_fromField() {
+        singleEntity(
+                """
+                @PrimaryKey
+                public int id;
+                @ColumnInfo(index = true)
+                public String foo;
+                """) { entity, invocation ->
+            assertThat(entity.indices, `is`(
+                    listOf(Index(name = "index_MyEntity_foo",
+                            unique = false,
+                            fields = fieldsByName(entity, "foo")))
+            ))
+        }.compilesWithoutError()
+    }
+
+    @Test
+    fun index_multiColumn() {
+        val annotation = mapOf(
+                "indices" to """@Index({"foo", "id"})"""
+        )
+        singleEntity(
+                """
+                @PrimaryKey
+                public int id;
+                public String foo;
+                """
+                , annotation) { entity, invocation ->
+            assertThat(entity.indices, `is`(
+                    listOf(Index(name = "index_MyEntity_foo_id",
+                            unique = false,
+                            fields = fieldsByName(entity, "foo", "id")))
+            ))
+        }.compilesWithoutError()
+    }
+
+    @Test
+    fun index_multiple() {
+        val annotation = mapOf(
+                "indices" to """{@Index({"foo", "id"}), @Index({"bar_column", "foo"})}"""
+        )
+        singleEntity(
+                """
+                @PrimaryKey
+                public int id;
+                public String foo;
+                @ColumnInfo(name = "bar_column")
+                public String bar;
+                """
+                , annotation) { entity, invocation ->
+            assertThat(entity.indices, `is`(
+                    listOf(Index(name = "index_MyEntity_foo_id",
+                            unique = false,
+                            fields = fieldsByName(entity, "foo", "id")),
+                            Index(name = "index_MyEntity_bar_column_foo",
+                                    unique = false,
+                                    fields = fieldsByName(entity, "bar", "foo")))
+            ))
+        }.compilesWithoutError()
+    }
+
+    @Test
+    fun index_unique() {
+        val annotation = mapOf(
+                "indices" to """@Index(value = {"foo", "id"}, unique = true)"""
+        )
+        singleEntity(
+                """
+                @PrimaryKey
+                public int id;
+                public String foo;
+                """
+                , annotation) { entity, invocation ->
+            assertThat(entity.indices, `is`(
+                    listOf(Index(name = "index_MyEntity_foo_id",
+                            unique = true,
+                           fields = fieldsByName(entity, "foo", "id")))
+            ))
+        }.compilesWithoutError()
+    }
+
+    @Test
+    fun index_customName() {
+        val annotation = mapOf(
+                "indices" to """@Index(value = {"foo"}, name = "myName")"""
+        )
+        singleEntity(
+                """
+                @PrimaryKey
+                public int id;
+                public String foo;
+                """
+                , annotation) { entity, invocation ->
+            assertThat(entity.indices, `is`(
+                    listOf(Index(name = "myName",
+                            unique = false,
+                            fields = fieldsByName(entity, "foo")))
+            ))
+        }.compilesWithoutError()
+    }
+
+    @Test
+    fun index_customTableName() {
+        val annotation = mapOf(
+                "tableName" to "\"MyTable\"",
+                "indices" to """@Index(value = {"foo"})"""
+        )
+        singleEntity(
+                """
+                @PrimaryKey
+                public int id;
+                public String foo;
+                """
+                , annotation) { entity, invocation ->
+            assertThat(entity.indices, `is`(
+                    listOf(Index(name = "index_MyTable_foo",
+                            unique = false,
+                            fields = fieldsByName(entity, "foo")))
+            ))
+        }.compilesWithoutError()
+    }
+
+    @Test
+    fun index_empty() {
+        val annotation = mapOf(
+                "indices" to """@Index({})"""
+        )
+        singleEntity(
+                """
+                @PrimaryKey
+                public int id;
+                public String foo;
+                """
+                , annotation) { entity, invocation ->
+        }.failsToCompile().withErrorContaining(
+                ProcessorErrors.INDEX_COLUMNS_CANNOT_BE_EMPTY
+        )
+    }
+
+    @Test
+    fun index_missingColumn() {
+        val annotation = mapOf(
+                "indices" to """@Index({"foo", "bar"})"""
+        )
+        singleEntity(
+                """
+                @PrimaryKey
+                public int id;
+                public String foo;
+                """
+                , annotation) { entity, invocation ->
+        }.failsToCompile().withErrorContaining(
+                ProcessorErrors.indexColumnDoesNotExist("bar", listOf("id, foo"))
+        )
+    }
+
+    @Test
+    fun index_nameConflict() {
+        val annotation = mapOf(
+                "indices" to """@Index({"foo"})"""
+        )
+        singleEntity(
+                """
+                @PrimaryKey
+                public int id;
+                @ColumnInfo(index = true)
+                public String foo;
+                """
+                , annotation) { entity, invocation ->
+        }.failsToCompile().withErrorContaining(
+                ProcessorErrors.duplicateIndexInEntity("index_MyEntity_foo")
+        )
+    }
+
+    @Test
+    fun index_droppedParentFieldIndex() {
+        val parent = JavaFileObjects.forSourceLines("foo.bar.Base",
+                """
+                package foo.bar;
+                import android.arch.persistence.room.*;
+                public class Base {
+                    @PrimaryKey
+                    long baseId;
+                    @ColumnInfo(index = true)
+                    String name;
+                    String lastName;
+                }
+                """)
+        singleEntity(
+                """
+                @PrimaryKey
+                public int id;
+                """, baseClass = "foo.bar.Base", jfos = listOf(parent)) { entity, invocation ->
+            assertThat(entity.indices.isEmpty(), `is`(true))
+        }.compilesWithoutError()
+                .withWarningContaining(
+                        ProcessorErrors.droppedSuperClassFieldIndex(
+                                fieldName = "name",
+                                childEntity = "foo.bar.MyEntity",
+                                superEntity = "foo.bar.Base")
+                )
+    }
+
+    @Test
+    fun index_keptGrandParentEntityIndex() {
+        val grandParent = JavaFileObjects.forSourceLines("foo.bar.Base",
+                """
+                package foo.bar;
+                import android.arch.persistence.room.*;
+                @Entity(indices = @Index({"name", "lastName"}))
+                public class Base {
+                    @PrimaryKey
+                    long baseId;
+                    String name, lastName;
+                }
+                """)
+        val parent = JavaFileObjects.forSourceLines("foo.bar.Parent",
+                """
+                package foo.bar;
+                import android.arch.persistence.room.*;
+
+                public class Parent extends Base {
+                    String iHaveAField;
+                }
+                """)
+        singleEntity(
+                """
+                @PrimaryKey
+                public int id;
+                """,
+                baseClass = "foo.bar.Parent",
+                attributes = hashMapOf("inheritSuperIndices" to "true"),
+                jfos = listOf(parent, grandParent)) {
+            entity, invocation ->
+            assertThat(entity.indices.size, `is`(1))
+            assertThat(entity.indices.first(),
+                    `is`(Index(name = "index_MyEntity_name_lastName",
+                            unique = false,
+                            fields = fieldsByName(entity, "name", "lastName"))))
+        }.compilesWithoutError().withWarningCount(0)
+    }
+
+    @Test
+    fun index_keptParentEntityIndex() {
+        val parent = JavaFileObjects.forSourceLines("foo.bar.Base",
+                """
+                package foo.bar;
+                import android.arch.persistence.room.*;
+                @Entity(indices = @Index({"name", "lastName"}))
+                public class Base {
+                    @PrimaryKey
+                    long baseId;
+                    String name, lastName;
+                }
+                """)
+        singleEntity(
+                """
+                @PrimaryKey
+                public int id;
+                """,
+                baseClass = "foo.bar.Base",
+                attributes = hashMapOf("inheritSuperIndices" to "true"),
+                jfos = listOf(parent)) { entity, invocation ->
+            assertThat(entity.indices.size, `is`(1))
+            assertThat(entity.indices.first(),
+                    `is`(Index(name = "index_MyEntity_name_lastName",
+                            unique = false,
+                            fields = fieldsByName(entity, "name", "lastName"))))
+        }.compilesWithoutError().withWarningCount(0)
+    }
+
+    @Test
+    fun index_keptParentFieldIndex() {
+        val parent = JavaFileObjects.forSourceLines("foo.bar.Base",
+                """
+                package foo.bar;
+                import android.arch.persistence.room.*;
+                public class Base {
+                    @PrimaryKey
+                    long baseId;
+                    @ColumnInfo(index = true)
+                    String name;
+                    String lastName;
+                }
+                """)
+        singleEntity(
+                """
+                @PrimaryKey
+                public int id;
+                """,
+                baseClass = "foo.bar.Base",
+                attributes = hashMapOf("inheritSuperIndices" to "true"),
+                jfos = listOf(parent)) { entity, invocation ->
+            assertThat(entity.indices.size, `is`(1))
+            assertThat(entity.indices.first(),
+                    `is`(Index(name = "index_MyEntity_name",
+                            unique = false,
+                            fields = fieldsByName(entity, "name"))))
+        }.compilesWithoutError().withWarningCount(0)
+
+    }
+
+    @Test
+    fun index_droppedGrandParentEntityIndex() {
+        val grandParent = JavaFileObjects.forSourceLines("foo.bar.Base",
+                """
+                package foo.bar;
+                import android.arch.persistence.room.*;
+                @Entity(indices = @Index({"name", "lastName"}))
+                public class Base {
+                    @PrimaryKey
+                    long baseId;
+                    String name, lastName;
+                }
+                """)
+        val parent = JavaFileObjects.forSourceLines("foo.bar.Parent",
+                """
+                package foo.bar;
+                import android.arch.persistence.room.*;
+
+                public class Parent extends Base {
+                    String iHaveAField;
+                }
+                """)
+        singleEntity(
+                """
+                @PrimaryKey
+                public int id;
+                """, baseClass = "foo.bar.Parent", jfos = listOf(parent, grandParent)) {
+            entity, invocation ->
+            assertThat(entity.indices.isEmpty(), `is`(true))
+        }.compilesWithoutError()
+                .withWarningContaining(
+                        ProcessorErrors.droppedSuperClassIndex(
+                                childEntity = "foo.bar.MyEntity",
+                                superEntity = "foo.bar.Base")
+                )
+    }
+
+    @Test
+    fun index_droppedParentEntityIndex() {
+        val parent = JavaFileObjects.forSourceLines("foo.bar.Base",
+                """
+                package foo.bar;
+                import android.arch.persistence.room.*;
+                @Entity(indices = @Index({"name", "lastName"}))
+                public class Base {
+                    @PrimaryKey
+                    long baseId;
+                    String name, lastName;
+                }
+                """)
+        singleEntity(
+                """
+                @PrimaryKey
+                public int id;
+                """, baseClass = "foo.bar.Base", jfos = listOf(parent)) { entity, invocation ->
+            assertThat(entity.indices.isEmpty(), `is`(true))
+        }.compilesWithoutError()
+                .withWarningContaining(
+                        ProcessorErrors.droppedSuperClassIndex(
+                                childEntity = "foo.bar.MyEntity",
+                                superEntity = "foo.bar.Base")
+                )
+    }
+
+    @Test
+    fun index_droppedEmbeddedEntityIndex() {
+        singleEntity(
+                """
+                @PrimaryKey
+                public int id;
+                @Embedded
+                public Foo foo;
+                @Entity(indices = {@Index("a")})
+                static class Foo {
+                    @PrimaryKey
+                    @ColumnInfo(name = "foo_id")
+                    int id;
+                    @ColumnInfo(index = true)
+                    public int a;
+                }
+                """) { entity, invocation ->
+            assertThat(entity.indices.isEmpty(), `is`(true))
+        }.compilesWithoutError()
+                .withWarningContaining(
+                        ProcessorErrors.droppedEmbeddedIndex(
+                                entityName = "foo.bar.MyEntity.Foo",
+                                fieldPath = "foo",
+                                grandParent = "foo.bar.MyEntity")
+                )
+    }
+
+    @Test
+    fun index_onEmbeddedField() {
+        singleEntity(
+                """
+                @PrimaryKey
+                public int id;
+                @Embedded
+                @ColumnInfo(index = true)
+                public Foo foo;
+                static class Foo {
+                    @ColumnInfo(index = true)
+                    public int a;
+                }
+                """) { entity, invocation ->
+            assertThat(entity.indices.isEmpty(), `is`(true))
+        }.failsToCompile().withErrorContaining(
+                ProcessorErrors.CANNOT_USE_MORE_THAN_ONE_POJO_FIELD_ANNOTATION
+        )
+    }
+
+    @Test
+    fun index_droppedEmbeddedFieldIndex() {
+        singleEntity(
+                """
+                @PrimaryKey
+                public int id;
+                @Embedded
+                public Foo foo;
+                static class Foo {
+                    @ColumnInfo(index = true)
+                    public int a;
+                }
+                """) { entity, invocation ->
+            assertThat(entity.indices.isEmpty(), `is`(true))
+        }.compilesWithoutError()
+                .withWarningContaining(
+                        ProcessorErrors.droppedEmbeddedFieldIndex("foo > a", "foo.bar.MyEntity")
+                )
+    }
+
+    @Test
+    fun index_referenceEmbeddedField() {
+        singleEntity(
+                """
+                @PrimaryKey
+                public int id;
+                @Embedded
+                public Foo foo;
+                static class Foo {
+                    public int a;
+                }
+                """, attributes = mapOf("indices" to "@Index(\"a\")")) { entity, invocation ->
+            assertThat(entity.indices.size, `is`(1))
+            assertThat(entity.indices.first(), `is`(
+                    Index(
+                            name = "index_MyEntity_a",
+                            unique = false,
+                            fields = fieldsByName(entity, "a")
+                    )
+            ))
+        }.compilesWithoutError()
+    }
+
+    @Test
+    fun primaryKey_definedInBothWays() {
+        singleEntity(
+                """
+                public int id;
+                @PrimaryKey
+                public String foo;
+                """,
+                attributes = mapOf("primaryKeys" to "\"id\"")) { entity, invocation ->
+        }.failsToCompile().withErrorContaining(
+                ProcessorErrors.multiplePrimaryKeyAnnotations(
+                        listOf("PrimaryKey[id]", "PrimaryKey[foo]")
+                ))
+    }
+
+    @Test
+    fun primaryKey_badColumnName() {
+        singleEntity(
+                """
+                public int id;
+                """,
+                attributes = mapOf("primaryKeys" to "\"foo\"")) { entity, invocation ->
+        }.failsToCompile().withErrorContaining(
+                ProcessorErrors.primaryKeyColumnDoesNotExist("foo", listOf("id")))
+    }
+
+    @Test
+    fun primaryKey_multipleAnnotations() {
+        singleEntity("""
+                @PrimaryKey
+                int x;
+                @PrimaryKey
+                int y;
+                """) { entity, invocation ->
+            assertThat(entity.primaryKey.fields.isEmpty(), `is`(true))
+        }.failsToCompile()
+                .withErrorContaining(
+                        ProcessorErrors.multiplePrimaryKeyAnnotations(
+                                listOf("PrimaryKey[x]", "PrimaryKey[y]")))
+    }
+
+    @Test
+    fun primaryKey_fromParentField() {
+        val parent = JavaFileObjects.forSourceLines("foo.bar.Base",
+                """
+                package foo.bar;
+                import android.arch.persistence.room.*;
+                public class Base {
+                    @PrimaryKey
+                    long baseId;
+                    String name, lastName;
+                }
+                """)
+        singleEntity(
+                """
+                public int id;
+                """,
+                baseClass = "foo.bar.Base",
+                jfos = listOf(parent)) { entity, invocation ->
+            assertThat(entity.primaryKey.fields.firstOrNull()?.name, `is`("baseId"))
+
+        }.compilesWithoutError().withWarningCount(0)
+    }
+
+    @Test
+    fun primaryKey_fromParentEntity() {
+        val parent = JavaFileObjects.forSourceLines("foo.bar.Base",
+                """
+                package foo.bar;
+                import android.arch.persistence.room.*;
+                @Entity(primaryKeys = "baseId")
+                public class Base {
+                    long baseId;
+                    String name, lastName;
+                }
+                """)
+        singleEntity(
+                """
+                public int id;
+                """,
+                baseClass = "foo.bar.Base",
+                jfos = listOf(parent)) { entity, invocation ->
+            assertThat(entity.primaryKey.fields.firstOrNull()?.name, `is`("baseId"))
+        }.compilesWithoutError().withWarningCount(0)
+    }
+
+    @Test
+    fun primaryKey_overrideFromParentField() {
+        val parent = JavaFileObjects.forSourceLines("foo.bar.Base",
+                """
+                package foo.bar;
+                import android.arch.persistence.room.*;
+                public class Base {
+                    @PrimaryKey
+                    long baseId;
+                    String name, lastName;
+                }
+                """)
+        singleEntity(
+                """
+                @PrimaryKey
+                public int id;
+                """,
+                baseClass = "foo.bar.Base",
+                jfos = listOf(parent)) { entity, invocation ->
+            assertThat(entity.primaryKey.fields.size, `is`(1))
+            assertThat(entity.primaryKey.fields.firstOrNull()?.name, `is`("id"))
+            assertThat(entity.primaryKey.autoGenerateId, `is`(false))
+        }.compilesWithoutError().withNoteContaining(
+                "PrimaryKey[baseId] is overridden by PrimaryKey[id]"
+        )
+    }
+
+    @Test
+    fun primaryKey_overrideFromParentEntityViaField() {
+        val parent = JavaFileObjects.forSourceLines("foo.bar.Base",
+                """
+                package foo.bar;
+                import android.arch.persistence.room.*;
+                @Entity(primaryKeys = "baseId")
+                public class Base {
+                    long baseId;
+                    String name, lastName;
+                }
+                """)
+        singleEntity(
+                """
+                @PrimaryKey
+                public int id;
+                """,
+                baseClass = "foo.bar.Base",
+                jfos = listOf(parent)) { entity, invocation ->
+            assertThat(entity.primaryKey.fields.size, `is`(1))
+            assertThat(entity.primaryKey.fields.firstOrNull()?.name, `is`("id"))
+        }.compilesWithoutError().withNoteContaining(
+                "PrimaryKey[baseId] is overridden by PrimaryKey[id]"
+        )
+    }
+
+    @Test
+    fun primaryKey_overrideFromParentEntityViaEntity() {
+        val parent = JavaFileObjects.forSourceLines("foo.bar.Base",
+                """
+                package foo.bar;
+                import android.arch.persistence.room.*;
+                @Entity(primaryKeys = "baseId")
+                public class Base {
+                    long baseId;
+                    String name, lastName;
+                }
+                """)
+        singleEntity(
+                """
+                public int id;
+                """,
+                baseClass = "foo.bar.Base",
+                jfos = listOf(parent),
+                attributes = mapOf("primaryKeys" to "\"id\"")) { entity, invocation ->
+            assertThat(entity.primaryKey.fields.size, `is`(1))
+            assertThat(entity.primaryKey.fields.firstOrNull()?.name, `is`("id"))
+            assertThat(entity.primaryKey.autoGenerateId, `is`(false))
+        }.compilesWithoutError().withNoteContaining(
+                "PrimaryKey[baseId] is overridden by PrimaryKey[id]"
+        )
+    }
+
+    @Test
+    fun primaryKey_autoGenerate() {
+        listOf("long", "Long", "Integer", "int").forEach { type ->
+            singleEntity(
+                    """
+                @PrimaryKey(autoGenerate = true)
+                public $type id;
+                """) { entity, invocation ->
+                assertThat(entity.primaryKey.fields.size, `is`(1))
+                assertThat(entity.primaryKey.fields.firstOrNull()?.name, `is`("id"))
+                assertThat(entity.primaryKey.autoGenerateId, `is`(true))
+            }.compilesWithoutError()
+        }
+    }
+
+    @Test
+    fun primaryKey_autoGenerateBadType() {
+        listOf("String", "float", "Float", "Double", "double").forEach { type ->
+            singleEntity(
+                    """
+                @PrimaryKey(autoGenerate = true)
+                public $type id;
+                """) { entity, invocation ->
+                assertThat(entity.primaryKey.fields.size, `is`(1))
+                assertThat(entity.primaryKey.fields.firstOrNull()?.name, `is`("id"))
+                assertThat(entity.primaryKey.autoGenerateId, `is`(true))
+            }.failsToCompile().withErrorContaining(
+                    ProcessorErrors.AUTO_INCREMENTED_PRIMARY_KEY_IS_NOT_INT)
+        }
+    }
+
+    @Test
+    fun primaryKey_embedded(){
+        singleEntity(
+                """
+                public int id;
+
+                @Embedded(prefix = "bar_")
+                @PrimaryKey
+                public Foo foo;
+
+                static class Foo {
+                    public int a;
+                    public int b;
+                }
+                """) { entity, invocation ->
+            assertThat(entity.primaryKey.fields.map { it.columnName },
+                    `is`(listOf("bar_a", "bar_b")))
+        }.compilesWithoutError().withWarningCount(0)
+    }
+
+    @Test
+    fun primaryKey_embeddedInherited(){
+        val parent = JavaFileObjects.forSourceLines("foo.bar.Base",
+                """
+                package foo.bar;
+                import android.arch.persistence.room.*;
+
+                public class Base {
+                    long baseId;
+                    String name, lastName;
+                    @Embedded(prefix = "bar_")
+                    @PrimaryKey
+                    public Foo foo;
+
+                    static class Foo {
+                        public int a;
+                        public int b;
+                    }
+                }
+                """)
+        singleEntity(
+                """
+                public int id;
+                """,
+                baseClass = "foo.bar.Base",
+                jfos = listOf(parent)) { entity, invocation ->
+            assertThat(entity.primaryKey.fields.map { it.columnName },
+                    `is`(listOf("bar_a", "bar_b")))
+        }.compilesWithoutError().withWarningCount(0)
+    }
+
+    @Test
+    fun primaryKey_overrideViaEmbedded() {
+        val parent = JavaFileObjects.forSourceLines("foo.bar.Base",
+                """
+                package foo.bar;
+                import android.arch.persistence.room.*;
+
+                @Entity(primaryKeys = "baseId")
+                public class Base {
+                    long baseId;
+                    String name, lastName;
+                }
+                """)
+        singleEntity(
+                """
+                public int id;
+                @Embedded(prefix = "bar_")
+                @PrimaryKey
+                public Foo foo;
+
+                static class Foo {
+                    public int a;
+                    public int b;
+                }
+                """,
+                baseClass = "foo.bar.Base",
+                jfos = listOf(parent)) { entity, invocation ->
+            assertThat(entity.primaryKey.fields.map { it.columnName },
+                    `is`(listOf("bar_a", "bar_b")))
+        }.compilesWithoutError().withNoteContaining(
+                "PrimaryKey[baseId] is overridden by PrimaryKey[foo > a, foo > b]")
+    }
+
+    @Test
+    fun primaryKey_overrideEmbedded() {
+        val parent = JavaFileObjects.forSourceLines("foo.bar.Base",
+                """
+                package foo.bar;
+                import android.arch.persistence.room.*;
+
+                public class Base {
+                    long baseId;
+                    String name, lastName;
+                    @Embedded(prefix = "bar_")
+                    @PrimaryKey
+                    public Foo foo;
+
+                    static class Foo {
+                        public int a;
+                        public int b;
+                    }
+                }
+                """)
+        singleEntity(
+                """
+                @PrimaryKey
+                public int id;
+                """,
+                baseClass = "foo.bar.Base",
+                jfos = listOf(parent)) { entity, invocation ->
+            assertThat(entity.primaryKey.fields.map { it.columnName },
+                    `is`(listOf("id")))
+        }.compilesWithoutError().withNoteContaining(
+                "PrimaryKey[foo > a, foo > b] is overridden by PrimaryKey[id]")
+    }
+
+    @Test
+    fun relationInEntity() {
+        singleEntity(
+                """
+                @PrimaryKey
+                int id;
+                @Relation(parentColumn = "id", entityColumn = "uid")
+                java.util.List<User> users;
+                """, jfos = listOf(COMMON.USER)
+        ) { entity, invocation ->
+        }.failsToCompile().withErrorContaining(RELATION_IN_ENTITY)
+    }
+
+    @Test
+    fun foreignKey_invalidAction() {
+        val annotation = mapOf(
+                "foreignKeys" to """{@ForeignKey(
+                    entity = ${COMMON.USER_TYPE_NAME}.class,
+                    parentColumns = "lastName",
+                    childColumns = "name",
+                    onDelete = 101
+                )}""".trimIndent()
+        )
+        singleEntity(
+                """
+                @PrimaryKey
+                int id;
+                String name;
+                """,
+                attributes = annotation, jfos = listOf(COMMON.USER)
+        ){ entity, invocation ->
+        }.failsToCompile().withErrorContaining(ProcessorErrors.INVALID_FOREIGN_KEY_ACTION)
+    }
+
+    @Test
+    fun foreignKey_badEntity() {
+        val annotation = mapOf(
+                "foreignKeys" to """{@ForeignKey(
+                    entity = dsa.class,
+                    parentColumns = "lastName",
+                    childColumns = "name"
+                )}""".trimIndent()
+        )
+        singleEntity(
+                """
+                @PrimaryKey
+                int id;
+                String name;
+                """,
+                attributes = annotation, jfos = listOf(COMMON.USER)
+        ){ entity, invocation ->
+        }.failsToCompile().withErrorContaining("cannot find symbol")
+    }
+
+    @Test
+    fun foreignKey_notAnEntity() {
+        val annotation = mapOf(
+                "foreignKeys" to """{@ForeignKey(
+                    entity = ${COMMON.NOT_AN_ENTITY_TYPE_NAME}.class,
+                    parentColumns = "lastName",
+                    childColumns = "name"
+                )}""".trimIndent()
+        )
+        singleEntity(
+                """
+                @PrimaryKey
+                int id;
+                String name;
+                """,
+                attributes = annotation, jfos = listOf(COMMON.NOT_AN_ENTITY)
+        ){ entity, invocation ->
+        }.failsToCompile().withErrorContaining(ProcessorErrors.foreignKeyNotAnEntity(
+                COMMON.NOT_AN_ENTITY_TYPE_NAME.toString()))
+    }
+
+    @Test
+    fun foreignKey_invalidChildColumn() {
+        val annotation = mapOf(
+                "foreignKeys" to """{@ForeignKey(
+                    entity = ${COMMON.USER_TYPE_NAME}.class,
+                    parentColumns = "lastName",
+                    childColumns = "namex"
+                )}""".trimIndent()
+        )
+        singleEntity(
+                """
+                @PrimaryKey
+                int id;
+                String name;
+                """,
+                attributes = annotation, jfos = listOf(COMMON.USER)
+        ){ entity, invocation ->
+        }.failsToCompile().withErrorContaining(ProcessorErrors.foreignKeyChildColumnDoesNotExist(
+                "namex", listOf("id", "name")))
+    }
+
+    @Test
+    fun foreignKey_columnCountMismatch() {
+        val annotation = mapOf(
+                "foreignKeys" to """{@ForeignKey(
+                    entity = ${COMMON.USER_TYPE_NAME}.class,
+                    parentColumns = "lastName",
+                    childColumns = {"name", "id"}
+                )}""".trimIndent()
+        )
+        singleEntity(
+                """
+                @PrimaryKey
+                int id;
+                String name;
+                """,
+                attributes = annotation, jfos = listOf(COMMON.USER)
+        ){ entity, invocation ->
+        }.failsToCompile().withErrorContaining(ProcessorErrors.foreignKeyColumnNumberMismatch(
+                listOf("name", "id"), listOf("lastName")))
+    }
+
+    @Test
+    fun foreignKey_emptyChildColumns() {
+        val annotation = mapOf(
+                "foreignKeys" to """{@ForeignKey(
+                    entity = ${COMMON.USER_TYPE_NAME}.class,
+                    parentColumns = "lastName",
+                    childColumns = {}
+                )}""".trimIndent()
+        )
+        singleEntity(
+                """
+                @PrimaryKey
+                int id;
+                String name;
+                """,
+                attributes = annotation, jfos = listOf(COMMON.USER)
+        ){ entity, invocation ->
+        }.failsToCompile().withErrorContaining(ProcessorErrors.FOREIGN_KEY_EMPTY_CHILD_COLUMN_LIST)
+    }
+
+    @Test
+    fun foreignKey_emptyParentColumns() {
+        val annotation = mapOf(
+                "foreignKeys" to """{@ForeignKey(
+                    entity = ${COMMON.USER_TYPE_NAME}.class,
+                    parentColumns = {},
+                    childColumns = {"name"}
+                )}""".trimIndent()
+        )
+        singleEntity(
+                """
+                @PrimaryKey
+                int id;
+                String name;
+                """,
+                attributes = annotation, jfos = listOf(COMMON.USER)
+        ){ entity, invocation ->
+        }.failsToCompile().withErrorContaining(ProcessorErrors.FOREIGN_KEY_EMPTY_PARENT_COLUMN_LIST)
+    }
+
+    @Test
+    fun foreignKey_simple() {
+        val annotation = mapOf(
+                "foreignKeys" to """{@ForeignKey(
+                    entity = ${COMMON.USER_TYPE_NAME}.class,
+                    parentColumns = "lastName",
+                    childColumns = "name",
+                    onDelete = ForeignKey.SET_NULL,
+                    onUpdate = ForeignKey.CASCADE,
+                    deferred = true
+                )}""".trimIndent()
+        )
+        singleEntity(
+                """
+                @PrimaryKey
+                int id;
+                String name;
+                """,
+                attributes = annotation, jfos = listOf(COMMON.USER)
+        ){ entity, invocation ->
+            assertThat(entity.foreignKeys.size, `is`(1))
+            val fKey = entity.foreignKeys.first()
+            assertThat(fKey.parentTable, `is`("User"))
+            assertThat(fKey.parentColumns, `is`(listOf("lastName")))
+            assertThat(fKey.deferred, `is`(true))
+            assertThat(fKey.childFields.size, `is`(1))
+            val field = fKey.childFields.first()
+            assertThat(field.name, `is`("name"))
+        }.compilesWithoutError()
+    }
+
+    @Test
+    fun foreignKey_dontDuplicationChildIndex_SingleColumn() {
+        val annotation = mapOf(
+                "foreignKeys" to """{@ForeignKey(
+                    entity = ${COMMON.USER_TYPE_NAME}.class,
+                    parentColumns = "lastName",
+                    childColumns = "name",
+                    onDelete = ForeignKey.SET_NULL,
+                    onUpdate = ForeignKey.CASCADE,
+                    deferred = true
+                )}""".trimIndent(),
+                "indices" to """@Index("name")"""
+        )
+        singleEntity(
+                """
+                @PrimaryKey
+                int id;
+                String name;
+                """,
+                attributes = annotation, jfos = listOf(COMMON.USER)
+        ) { entity, invocation ->
+        }.compilesWithoutWarnings()
+    }
+
+    @Test
+    fun foreignKey_dontDuplicationChildIndex_MultipleColumns() {
+        val annotation = mapOf(
+                "foreignKeys" to """{@ForeignKey(
+                    entity = ${COMMON.USER_TYPE_NAME}.class,
+                    parentColumns = {"lastName", "name"},
+                    childColumns = {"lName", "name"},
+                    onDelete = ForeignKey.SET_NULL,
+                    onUpdate = ForeignKey.CASCADE,
+                    deferred = true
+                )}""".trimIndent(),
+                "indices" to """@Index({"lName", "name"})"""
+        )
+        singleEntity(
+                """
+                @PrimaryKey
+                int id;
+                String name;
+                String lName;
+                """,
+                attributes = annotation, jfos = listOf(COMMON.USER)
+        ) { entity, invocation ->
+            assertThat(entity.indices.size, `is`(1))
+        }.compilesWithoutWarnings()
+    }
+
+    @Test
+    fun foreignKey_dontDuplicationChildIndex_WhenCovered() {
+        val annotation = mapOf(
+                "foreignKeys" to """{@ForeignKey(
+                    entity = ${COMMON.USER_TYPE_NAME}.class,
+                    parentColumns = {"lastName"},
+                    childColumns = {"name"},
+                    onDelete = ForeignKey.SET_NULL,
+                    onUpdate = ForeignKey.CASCADE,
+                    deferred = true
+                )}""".trimIndent(),
+                "indices" to """@Index({"name", "lName"})"""
+        )
+        singleEntity(
+                """
+                @PrimaryKey
+                int id;
+                String name;
+                String lName;
+                """,
+                attributes = annotation, jfos = listOf(COMMON.USER)
+        ) { entity, invocation ->
+            assertThat(entity.indices.size, `is`(1))
+        }.compilesWithoutWarnings()
+    }
+
+    @Test
+    fun foreignKey_warnMissingChildIndex() {
+        val annotation = mapOf(
+                "foreignKeys" to """{@ForeignKey(
+                    entity = ${COMMON.USER_TYPE_NAME}.class,
+                    parentColumns = "lastName",
+                    childColumns = "name",
+                    onDelete = ForeignKey.SET_NULL,
+                    onUpdate = ForeignKey.CASCADE,
+                    deferred = true
+                )}""".trimIndent()
+        )
+        singleEntity(
+                """
+                @PrimaryKey
+                int id;
+                String name;
+                """,
+                attributes = annotation, jfos = listOf(COMMON.USER)
+        ){ entity, invocation ->
+            assertThat(entity.indices, `is`(emptyList()))
+        }.compilesWithoutError().withWarningContaining(
+                ProcessorErrors.foreignKeyMissingIndexInChildColumn("name"))
+    }
+
+    @Test
+    fun foreignKey_warnMissingChildrenIndex() {
+        val annotation = mapOf(
+                "foreignKeys" to """{@ForeignKey(
+                    entity = ${COMMON.USER_TYPE_NAME}.class,
+                    parentColumns = {"lastName", "name"},
+                    childColumns = {"lName", "name"}
+                )}""".trimIndent()
+        )
+        singleEntity(
+                """
+                @PrimaryKey
+                int id;
+                String name;
+                String lName;
+                """,
+                attributes = annotation, jfos = listOf(COMMON.USER)
+        ){ entity, invocation ->
+            assertThat(entity.indices, `is`(emptyList()))
+        }.compilesWithoutError().withWarningContaining(
+                ProcessorErrors.foreignKeyMissingIndexInChildColumns(listOf("lName", "name")))
+    }
+
+    @Test
+    fun foreignKey_dontIndexIfAlreadyPrimaryKey() {
+        val annotation = mapOf(
+                "foreignKeys" to """{@ForeignKey(
+                    entity = ${COMMON.USER_TYPE_NAME}.class,
+                    parentColumns = "lastName",
+                    childColumns = "id",
+                    onDelete = ForeignKey.SET_NULL,
+                    onUpdate = ForeignKey.CASCADE,
+                    deferred = true
+                )}""".trimIndent()
+        )
+        singleEntity(
+                """
+                @PrimaryKey
+                int id;
+                String name;
+                """,
+                attributes = annotation, jfos = listOf(COMMON.USER)
+        ){ entity, invocation ->
+            assertThat(entity.indices, `is`(emptyList()))
+        }.compilesWithoutWarnings()
+    }
+}
diff --git a/room/compiler/src/test/kotlin/android/arch/persistence/room/processor/FieldProcessorTest.kt b/room/compiler/src/test/kotlin/android/arch/persistence/room/processor/FieldProcessorTest.kt
new file mode 100644
index 0000000..fb64c7c
--- /dev/null
+++ b/room/compiler/src/test/kotlin/android/arch/persistence/room/processor/FieldProcessorTest.kt
@@ -0,0 +1,364 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.arch.persistence.room.processor
+
+import android.arch.persistence.room.Entity
+import android.arch.persistence.room.parser.SQLTypeAffinity
+import android.arch.persistence.room.solver.types.ColumnTypeAdapter
+import android.arch.persistence.room.testing.TestInvocation
+import android.arch.persistence.room.testing.TestProcessor
+import android.arch.persistence.room.vo.Field
+import com.google.auto.common.MoreElements
+import com.google.auto.common.MoreTypes
+import com.google.common.truth.Truth
+import com.google.testing.compile.CompileTester
+import com.google.testing.compile.JavaFileObjects
+import com.google.testing.compile.JavaSourcesSubjectFactory
+import org.hamcrest.CoreMatchers.`is`
+import org.hamcrest.MatcherAssert.assertThat
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.junit.runners.JUnit4
+import org.mockito.Mockito.mock
+import simpleRun
+import javax.lang.model.element.Element
+import javax.lang.model.element.ElementKind
+import javax.lang.model.type.TypeKind
+import javax.lang.model.type.TypeMirror
+
+@Suppress("HasPlatformType")
+@RunWith(JUnit4::class)
+class FieldProcessorTest {
+    companion object {
+        const val ENTITY_PREFIX = """
+                package foo.bar;
+                import android.arch.persistence.room.*;
+                @Entity
+                abstract class MyEntity {
+                """
+        const val ENTITY_SUFFIX = "}"
+        val ALL_PRIMITIVES = arrayListOf(
+                TypeKind.INT,
+                TypeKind.BYTE,
+                TypeKind.SHORT,
+                TypeKind.LONG,
+                TypeKind.CHAR,
+                TypeKind.FLOAT,
+                TypeKind.DOUBLE)
+        val ARRAY_CONVERTER = JavaFileObjects.forSourceLines("foo.bar.MyConverter",
+                """
+                package foo.bar;
+                import android.arch.persistence.room.*;
+                public class MyConverter {
+                ${ALL_PRIMITIVES.joinToString("\n") {
+                    val arrayDef = "${it.name.toLowerCase()}[]"
+                    "@TypeConverter public static String" +
+                            " arrayIntoString($arrayDef input) { return null;}" +
+                            "@TypeConverter public static $arrayDef" +
+                            " stringIntoArray${it.name}(String input) { return null;}"
+                }}
+                ${ALL_PRIMITIVES.joinToString("\n") {
+                    val arrayDef = "${it.box()}[]"
+                    "@TypeConverter public static String" +
+                            " arrayIntoString($arrayDef input) { return null;}" +
+                            "@TypeConverter public static $arrayDef" +
+                            " stringIntoArray${it.name}Boxed(String input) { return null;}"
+                }}
+                }
+                """)
+
+        private fun TypeKind.box(): String {
+            return "java.lang." + when (this) {
+                TypeKind.INT -> "Integer"
+                TypeKind.CHAR -> "Character"
+                else -> this.name.toLowerCase().capitalize()
+            }
+        }
+
+        // these 2 box methods are ugly but makes tests nicer and they are private
+        private fun TypeKind.typeMirror(invocation: TestInvocation): TypeMirror {
+            return invocation.processingEnv.typeUtils.getPrimitiveType(this)
+        }
+
+        private fun TypeKind.affinity(): SQLTypeAffinity {
+            return when (this) {
+                TypeKind.FLOAT, TypeKind.DOUBLE -> SQLTypeAffinity.REAL
+                else -> SQLTypeAffinity.INTEGER
+            }
+        }
+
+        private fun TypeKind.box(invocation: TestInvocation): TypeMirror {
+            return invocation.processingEnv.elementUtils.getTypeElement(box()).asType()
+        }
+    }
+
+    @Test
+    fun primitives() {
+        ALL_PRIMITIVES.forEach { primitive ->
+            singleEntity("${primitive.name.toLowerCase()} x;") { field, invocation ->
+                assertThat(field, `is`(
+                        Field(name = "x",
+                                type = primitive.typeMirror(invocation),
+                                element = field.element,
+                                affinity = primitive.affinity()
+                        )))
+            }.compilesWithoutError()
+        }
+    }
+
+    @Test
+    fun boxed() {
+        ALL_PRIMITIVES.forEach { primitive ->
+            singleEntity("${primitive.box()} y;") { field, invocation ->
+                assertThat(field, `is`(
+                        Field(name = "y",
+                                type = primitive.box(invocation),
+                                element = field.element,
+                                affinity = primitive.affinity())))
+            }.compilesWithoutError()
+        }
+    }
+
+    @Test
+    fun columnName() {
+        singleEntity("""
+            @ColumnInfo(name = "foo")
+            @PrimaryKey
+            int x;
+            """) { field, invocation ->
+            assertThat(field, `is`(
+                    Field(name = "x",
+                            type = TypeKind.INT.typeMirror(invocation),
+                            element = field.element,
+                            columnName = "foo",
+                            affinity = SQLTypeAffinity.INTEGER)))
+        }.compilesWithoutError()
+    }
+
+    @Test
+    fun indexed() {
+        singleEntity("""
+            @ColumnInfo(name = "foo", index = true)
+            int x;
+            """) { field, invocation ->
+            assertThat(field, `is`(
+                    Field(name = "x",
+                            type = TypeKind.INT.typeMirror(invocation),
+                            element = field.element,
+                            columnName = "foo",
+                            affinity = SQLTypeAffinity.INTEGER,
+                            indexed = true)))
+        }.compilesWithoutError()
+    }
+
+    @Test
+    fun emptyColumnName() {
+        singleEntity("""
+            @ColumnInfo(name = "")
+            int x;
+            """) { field, invocation ->
+        }.failsToCompile().withErrorContaining(ProcessorErrors.COLUMN_NAME_CANNOT_BE_EMPTY)
+    }
+
+    @Test
+    fun byteArrayWithEnforcedType() {
+        singleEntity("@TypeConverters(foo.bar.MyConverter.class)" +
+                "@ColumnInfo(typeAffinity = ColumnInfo.TEXT) byte[] arr;") { field, invocation ->
+            assertThat(field, `is`(Field(name = "arr",
+                    type = invocation.processingEnv.typeUtils.getArrayType(
+                            TypeKind.BYTE.typeMirror(invocation)),
+                    element = field.element,
+                    affinity = SQLTypeAffinity.TEXT)))
+            assertThat((field.cursorValueReader as? ColumnTypeAdapter)?.typeAffinity,
+                    `is`(SQLTypeAffinity.TEXT))
+        }.compilesWithoutError()
+    }
+
+    @Test
+    fun primitiveArray() {
+        ALL_PRIMITIVES.forEach { primitive ->
+            singleEntity("@TypeConverters(foo.bar.MyConverter.class) " +
+                    "${primitive.toString().toLowerCase()}[] arr;") { field, invocation ->
+                assertThat(field, `is`(
+                        Field(name = "arr",
+                                type = invocation.processingEnv.typeUtils.getArrayType(
+                                        primitive.typeMirror(invocation)),
+                                element = field.element,
+                                affinity = if (primitive == TypeKind.BYTE) {
+                                    SQLTypeAffinity.BLOB
+                                } else {
+                                    SQLTypeAffinity.TEXT
+                                })))
+            }.compilesWithoutError()
+        }
+    }
+
+    @Test
+    fun boxedArray() {
+        ALL_PRIMITIVES.forEach { primitive ->
+            singleEntity("@TypeConverters(foo.bar.MyConverter.class) " +
+                    "${primitive.box()}[] arr;") { field, invocation ->
+                assertThat(field, `is`(
+                        Field(name = "arr",
+                                type = invocation.processingEnv.typeUtils.getArrayType(
+                                        primitive.box(invocation)),
+                                element = field.element,
+                                affinity = SQLTypeAffinity.TEXT)))
+            }.compilesWithoutError()
+        }
+    }
+
+    @Test
+    fun generic() {
+        singleEntity("""
+                static class BaseClass<T> {
+                    T item;
+                }
+                @Entity
+                static class Extending extends BaseClass<java.lang.Integer> {
+                }
+                """) { field, invocation ->
+            assertThat(field, `is`(Field(name = "item",
+                    type = TypeKind.INT.box(invocation),
+                    element = field.element,
+                    affinity = SQLTypeAffinity.INTEGER)))
+        }.compilesWithoutError()
+    }
+
+    @Test
+    fun unboundGeneric() {
+        singleEntity("""
+                @Entity
+                static class BaseClass<T> {
+                    T item;
+                }
+                """) { field, invocation -> }.failsToCompile()
+                .withErrorContaining(ProcessorErrors.CANNOT_USE_UNBOUND_GENERICS_IN_ENTITY_FIELDS)
+    }
+
+    @Test
+    fun nameVariations() {
+        simpleRun {
+            assertThat(Field(mock(Element::class.java), "x", TypeKind.INT.typeMirror(it),
+                    SQLTypeAffinity.INTEGER).nameWithVariations, `is`(arrayListOf("x")))
+            assertThat(Field(mock(Element::class.java), "x", TypeKind.BOOLEAN.typeMirror(it),
+                    SQLTypeAffinity.INTEGER).nameWithVariations, `is`(arrayListOf("x")))
+            assertThat(Field(mock(Element::class.java), "xAll",
+                    TypeKind.BOOLEAN.typeMirror(it), SQLTypeAffinity.INTEGER)
+                    .nameWithVariations, `is`(arrayListOf("xAll")))
+        }
+    }
+
+    @Test
+    fun nameVariations_is() {
+        val elm = mock(Element::class.java)
+        simpleRun {
+            assertThat(Field(elm, "isX", TypeKind.BOOLEAN.typeMirror(it),
+                    SQLTypeAffinity.INTEGER).nameWithVariations, `is`(arrayListOf("isX", "x")))
+            assertThat(Field(elm, "isX", TypeKind.INT.typeMirror(it),
+                    SQLTypeAffinity.INTEGER).nameWithVariations, `is`(arrayListOf("isX")))
+            assertThat(Field(elm, "is", TypeKind.BOOLEAN.typeMirror(it),
+                    SQLTypeAffinity.INTEGER).nameWithVariations, `is`(arrayListOf("is")))
+            assertThat(Field(elm, "isAllItems", TypeKind.BOOLEAN.typeMirror(it),
+                    SQLTypeAffinity.INTEGER).nameWithVariations,
+                    `is`(arrayListOf("isAllItems", "allItems")))
+        }
+    }
+
+    @Test
+    fun nameVariations_has() {
+        val elm = mock(Element::class.java)
+        simpleRun {
+            assertThat(Field(elm, "hasX", TypeKind.BOOLEAN.typeMirror(it),
+                    SQLTypeAffinity.INTEGER).nameWithVariations, `is`(arrayListOf("hasX", "x")))
+            assertThat(Field(elm, "hasX", TypeKind.INT.typeMirror(it),
+                    SQLTypeAffinity.INTEGER).nameWithVariations, `is`(arrayListOf("hasX")))
+            assertThat(Field(elm, "has", TypeKind.BOOLEAN.typeMirror(it),
+                    SQLTypeAffinity.INTEGER).nameWithVariations, `is`(arrayListOf("has")))
+            assertThat(Field(elm, "hasAllItems", TypeKind.BOOLEAN.typeMirror(it),
+                    SQLTypeAffinity.INTEGER).nameWithVariations,
+                    `is`(arrayListOf("hasAllItems", "allItems")))
+        }
+    }
+
+    @Test
+    fun nameVariations_m() {
+        val elm = mock(Element::class.java)
+        simpleRun {
+            assertThat(Field(elm, "mall", TypeKind.BOOLEAN.typeMirror(it),
+                    SQLTypeAffinity.INTEGER).nameWithVariations, `is`(arrayListOf("mall")))
+            assertThat(Field(elm, "mallVars", TypeKind.BOOLEAN.typeMirror(it),
+                    SQLTypeAffinity.INTEGER).nameWithVariations, `is`(arrayListOf("mallVars")))
+            assertThat(Field(elm, "mAll", TypeKind.BOOLEAN.typeMirror(it),
+                    SQLTypeAffinity.INTEGER).nameWithVariations, `is`(arrayListOf("mAll", "all")))
+            assertThat(Field(elm, "m", TypeKind.INT.typeMirror(it),
+                    SQLTypeAffinity.INTEGER).nameWithVariations, `is`(arrayListOf("m")))
+            assertThat(Field(elm, "mallItems", TypeKind.BOOLEAN.typeMirror(it),
+                    SQLTypeAffinity.INTEGER).nameWithVariations,
+                    `is`(arrayListOf("mallItems")))
+            assertThat(Field(elm, "mAllItems", TypeKind.BOOLEAN.typeMirror(it),
+                    SQLTypeAffinity.INTEGER).nameWithVariations,
+                    `is`(arrayListOf("mAllItems", "allItems")))
+        }
+    }
+
+    @Test
+    fun nameVariations_underscore() {
+        val elm = mock(Element::class.java)
+        simpleRun {
+            assertThat(Field(elm, "_all", TypeKind.BOOLEAN.typeMirror(it),
+                    SQLTypeAffinity.INTEGER).nameWithVariations, `is`(arrayListOf("_all", "all")))
+            assertThat(Field(elm, "_", TypeKind.INT.typeMirror(it),
+                    SQLTypeAffinity.INTEGER).nameWithVariations, `is`(arrayListOf("_")))
+            assertThat(Field(elm, "_allItems", TypeKind.BOOLEAN.typeMirror(it),
+                    SQLTypeAffinity.INTEGER).nameWithVariations,
+                    `is`(arrayListOf("_allItems", "allItems")))
+        }
+    }
+
+    fun singleEntity(vararg input: String, handler: (Field, invocation: TestInvocation) -> Unit):
+            CompileTester {
+        return Truth.assertAbout(JavaSourcesSubjectFactory.javaSources())
+                .that(listOf(JavaFileObjects.forSourceString("foo.bar.MyEntity",
+                        ENTITY_PREFIX + input.joinToString("\n") + ENTITY_SUFFIX
+                ), ARRAY_CONVERTER))
+                .processedWith(TestProcessor.builder()
+                        .forAnnotations(android.arch.persistence.room.Entity::class)
+                        .nextRunHandler { invocation ->
+                            val (owner, field) = invocation.roundEnv
+                                    .getElementsAnnotatedWith(Entity::class.java)
+                                    .map {
+                                        Pair(it, invocation.processingEnv.elementUtils
+                                                .getAllMembers(MoreElements.asType(it))
+                                                .firstOrNull { it.kind == ElementKind.FIELD })
+                                    }
+                                    .first { it.second != null }
+                            val entityContext =
+                                    EntityProcessor(invocation.context, MoreElements.asType(owner))
+                                            .context
+                            val parser = FieldProcessor(
+                                    baseContext = entityContext,
+                                    containing = MoreTypes.asDeclared(owner.asType()),
+                                    element = field!!,
+                                    bindingScope = FieldProcessor.BindingScope.TWO_WAY,
+                                    fieldParent = null)
+                            handler(parser.process(), invocation)
+                            true
+                        }
+                        .build())
+    }
+}
diff --git a/room/compiler/src/test/kotlin/android/arch/persistence/room/processor/InsertionMethodProcessorTest.kt b/room/compiler/src/test/kotlin/android/arch/persistence/room/processor/InsertionMethodProcessorTest.kt
new file mode 100644
index 0000000..aff6346
--- /dev/null
+++ b/room/compiler/src/test/kotlin/android/arch/persistence/room/processor/InsertionMethodProcessorTest.kt
@@ -0,0 +1,422 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.arch.persistence.room.processor
+
+import COMMON
+import android.arch.persistence.room.Dao
+import android.arch.persistence.room.Insert
+import android.arch.persistence.room.OnConflictStrategy
+import android.arch.persistence.room.ext.CommonTypeNames
+import android.arch.persistence.room.ext.typeName
+import android.arch.persistence.room.testing.TestInvocation
+import android.arch.persistence.room.testing.TestProcessor
+import android.arch.persistence.room.vo.InsertionMethod
+import android.arch.persistence.room.vo.InsertionMethod.Type
+import com.google.auto.common.MoreElements
+import com.google.auto.common.MoreTypes
+import com.google.common.truth.Truth.assertAbout
+import com.google.testing.compile.CompileTester
+import com.google.testing.compile.JavaFileObjects
+import com.google.testing.compile.JavaSourcesSubjectFactory
+import com.squareup.javapoet.ArrayTypeName
+import com.squareup.javapoet.ClassName
+import com.squareup.javapoet.ParameterizedTypeName
+import com.squareup.javapoet.TypeName
+import org.hamcrest.CoreMatchers.`is`
+import org.hamcrest.CoreMatchers.nullValue
+import org.hamcrest.MatcherAssert.assertThat
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.junit.runners.JUnit4
+
+@Suppress("PLATFORM_CLASS_MAPPED_TO_KOTLIN")
+@RunWith(JUnit4::class)
+class InsertionMethodProcessorTest {
+    companion object {
+        const val DAO_PREFIX = """
+                package foo.bar;
+                import android.arch.persistence.room.*;
+                import java.util.*;
+                @Dao
+                abstract class MyClass {
+                """
+        const val DAO_SUFFIX = "}"
+        val USER_TYPE_NAME : TypeName = COMMON.USER_TYPE_NAME
+        val BOOK_TYPE_NAME : TypeName = ClassName.get("foo.bar", "Book")
+    }
+
+    @Test
+    fun readNoParams() {
+        singleInsertMethod(
+                """
+                @Insert
+                abstract public void foo();
+                """) { insertion, invocation ->
+            assertThat(insertion.name, `is`("foo"))
+            assertThat(insertion.parameters.size, `is`(0))
+            assertThat(insertion.returnType.typeName(), `is`(TypeName.VOID))
+            assertThat(insertion.entities.size, `is`(0))
+        }.failsToCompile().withErrorContaining(
+                ProcessorErrors.INSERTION_DOES_NOT_HAVE_ANY_PARAMETERS_TO_INSERT)
+    }
+
+    @Test
+    fun insertSingle() {
+        singleInsertMethod(
+                """
+                @Insert
+                abstract public long foo(User user);
+                """) { insertion, invocation ->
+            assertThat(insertion.name, `is`("foo"))
+            assertThat(insertion.parameters.size, `is`(1))
+            val param = insertion.parameters.first()
+            assertThat(param.type.typeName(), `is`(USER_TYPE_NAME))
+            assertThat(param.entityType?.typeName(), `is`(USER_TYPE_NAME))
+            assertThat(insertion.entities["user"]?.typeName,
+                    `is`(ClassName.get("foo.bar", "User") as TypeName))
+            assertThat(insertion.returnType.typeName(), `is`(TypeName.LONG))
+        }.compilesWithoutError()
+    }
+
+    @Test
+    fun insertNotAnEntity() {
+        singleInsertMethod(
+                """
+                @Insert
+                abstract public void foo(NotAnEntity notValid);
+                """) { insertion, invocation ->
+            assertThat(insertion.name, `is`("foo"))
+            assertThat(insertion.parameters.size, `is`(1))
+            val param = insertion.parameters.first()
+            assertThat(param.entityType, `is`(nullValue()))
+            assertThat(insertion.entities.size, `is`(0))
+        }.failsToCompile().withErrorContaining(
+                ProcessorErrors.CANNOT_FIND_ENTITY_FOR_SHORTCUT_QUERY_PARAMETER
+        )
+    }
+
+    @Test
+    fun insertTwo() {
+        singleInsertMethod(
+                """
+                @Insert
+                abstract public void foo(User u1, User u2);
+                """) { insertion, invocation ->
+            assertThat(insertion.name, `is`("foo"))
+
+            assertThat(insertion.parameters.size, `is`(2))
+            insertion.parameters.forEach {
+                assertThat(it.type.typeName(), `is`(USER_TYPE_NAME))
+                assertThat(it.entityType?.typeName(), `is`(USER_TYPE_NAME))
+            }
+            assertThat(insertion.entities.size, `is`(2))
+            assertThat(insertion.entities["u1"]?.typeName, `is`(USER_TYPE_NAME))
+            assertThat(insertion.entities["u2"]?.typeName, `is`(USER_TYPE_NAME))
+            assertThat(insertion.parameters.map { it.name }, `is`(listOf("u1", "u2")))
+            assertThat(insertion.returnType.typeName(), `is`(TypeName.VOID))
+        }.compilesWithoutError()
+    }
+
+    @Test
+    fun insertList() {
+        singleInsertMethod(
+                """
+                @Insert
+                abstract public List<Long> insertUsers(List<User> users);
+                """) { insertion, invocation ->
+            assertThat(insertion.name, `is`("insertUsers"))
+            assertThat(insertion.parameters.size, `is`(1))
+            val param = insertion.parameters.first()
+            assertThat(param.type.typeName(), `is`(
+                    ParameterizedTypeName.get(
+                            ClassName.get("java.util", "List"), USER_TYPE_NAME) as TypeName))
+            assertThat(param.entityType?.typeName(), `is`(USER_TYPE_NAME))
+            assertThat(insertion.entities.size, `is`(1))
+            assertThat(insertion.entities["users"]?.typeName, `is`(USER_TYPE_NAME))
+            assertThat(insertion.returnType.typeName(), `is`(
+                    ParameterizedTypeName.get(ClassName.get("java.util", "List"),
+                            ClassName.get("java.lang", "Long")) as TypeName
+            ))
+        }.compilesWithoutError()
+    }
+
+    @Test
+    fun insertArray() {
+        singleInsertMethod(
+                """
+                @Insert
+                abstract public void insertUsers(User[] users);
+                """) { insertion, invocation ->
+            assertThat(insertion.name, `is`("insertUsers"))
+            assertThat(insertion.parameters.size, `is`(1))
+            val param = insertion.parameters.first()
+            assertThat(param.type.typeName(), `is`(
+                    ArrayTypeName.of(COMMON.USER_TYPE_NAME) as TypeName))
+            assertThat(insertion.entities.size, `is`(1))
+            assertThat(insertion.entities["users"]?.typeName, `is`(USER_TYPE_NAME))
+            assertThat(insertion.returnType.typeName(), `is`(TypeName.VOID))
+        }.compilesWithoutError()
+    }
+
+    @Test
+    fun insertSet() {
+        singleInsertMethod(
+                """
+                @Insert
+                abstract public void insertUsers(Set<User> users);
+                """) { insertion, invocation ->
+            assertThat(insertion.name, `is`("insertUsers"))
+            assertThat(insertion.parameters.size, `is`(1))
+            val param = insertion.parameters.first()
+            assertThat(param.type.typeName(), `is`(
+                    ParameterizedTypeName.get(ClassName.get("java.util", "Set")
+                            , COMMON.USER_TYPE_NAME) as TypeName))
+            assertThat(insertion.entities.size, `is`(1))
+            assertThat(insertion.entities["users"]?.typeName, `is`(USER_TYPE_NAME))
+            assertThat(insertion.returnType.typeName(), `is`(TypeName.VOID))
+        }.compilesWithoutError()
+    }
+
+    @Test
+    fun insertQueue() {
+        singleInsertMethod(
+                """
+                @Insert
+                abstract public void insertUsers(Queue<User> users);
+                """) { insertion, invocation ->
+            assertThat(insertion.name, `is`("insertUsers"))
+            assertThat(insertion.parameters.size, `is`(1))
+            val param = insertion.parameters.first()
+            assertThat(param.type.typeName(), `is`(
+                    ParameterizedTypeName.get(ClassName.get("java.util", "Queue")
+                            , USER_TYPE_NAME) as TypeName))
+            assertThat(insertion.entities.size, `is`(1))
+            assertThat(insertion.entities["users"]?.typeName, `is`(USER_TYPE_NAME))
+            assertThat(insertion.returnType.typeName(), `is`(TypeName.VOID))
+        }.compilesWithoutError()
+    }
+
+    @Test
+    fun insertIterable() {
+        singleInsertMethod("""
+                @Insert
+                abstract public void insert(Iterable<User> users);
+                """) { insertion, invocation ->
+            assertThat(insertion.name, `is`("insert"))
+            assertThat(insertion.parameters.size, `is`(1))
+            val param = insertion.parameters.first()
+            assertThat(param.type.typeName(), `is`(ParameterizedTypeName.get(
+                    ClassName.get("java.lang", "Iterable"), USER_TYPE_NAME) as TypeName))
+            assertThat(insertion.entities.size, `is`(1))
+            assertThat(insertion.entities["users"]?.typeName, `is`(USER_TYPE_NAME))
+            assertThat(insertion.returnType.typeName(), `is`(TypeName.VOID))
+        }.compilesWithoutError()
+    }
+
+    @Test
+    fun insertCustomCollection() {
+        singleInsertMethod("""
+                static class MyList<Irrelevant, Item> extends ArrayList<Item> {}
+                @Insert
+                abstract public void insert(MyList<String, User> users);
+                """) { insertion, invocation ->
+            assertThat(insertion.name, `is`("insert"))
+            assertThat(insertion.parameters.size, `is`(1))
+            val param = insertion.parameters.first()
+            assertThat(param.type.typeName(), `is`(ParameterizedTypeName.get(
+                    ClassName.get("foo.bar", "MyClass.MyList"),
+                    CommonTypeNames.STRING, USER_TYPE_NAME) as TypeName))
+            assertThat(insertion.entities.size, `is`(1))
+            assertThat(insertion.entities["users"]?.typeName, `is`(USER_TYPE_NAME))
+            assertThat(insertion.returnType.typeName(), `is`(TypeName.VOID))
+        }.compilesWithoutError()
+    }
+
+    @Test
+    fun insertDifferentTypes() {
+        singleInsertMethod(
+                """
+                @Insert
+                abstract public void foo(User u1, Book b1);
+                """) { insertion, invocation ->
+            assertThat(insertion.parameters.size, `is`(2))
+            assertThat(insertion.parameters[0].type.typeName().toString(),
+                    `is`("foo.bar.User"))
+            assertThat(insertion.parameters[1].type.typeName().toString(),
+                    `is`("foo.bar.Book"))
+            assertThat(insertion.parameters.map { it.name }, `is`(listOf("u1", "b1")))
+            assertThat(insertion.returnType.typeName(), `is`(TypeName.VOID))
+            assertThat(insertion.entities.size, `is`(2))
+            assertThat(insertion.entities["u1"]?.typeName, `is`(USER_TYPE_NAME))
+            assertThat(insertion.entities["b1"]?.typeName, `is`(BOOK_TYPE_NAME))
+        }.compilesWithoutError()
+    }
+
+    @Test
+    fun onConflict_Default() {
+        singleInsertMethod(
+                """
+                @Insert
+                abstract public void foo(User user);
+                """) { insertion, invocation ->
+            assertThat(insertion.onConflict, `is`(OnConflictStrategy.ABORT))
+        }.compilesWithoutError()
+    }
+
+    @Test
+    fun onConflict_Invalid() {
+        singleInsertMethod(
+                """
+                @Insert(onConflict = -1)
+                abstract public void foo(User user);
+                """) { insertion, invocation ->
+        }.failsToCompile().withErrorContaining(ProcessorErrors.INVALID_ON_CONFLICT_VALUE)
+    }
+
+    @Test
+    fun onConflict_EachValue() {
+        listOf(
+                Pair("REPLACE", 1),
+                Pair("ROLLBACK", 2),
+                Pair("ABORT", 3),
+                Pair("FAIL", 4),
+                Pair("IGNORE", 5)
+        ).forEach { pair ->
+            singleInsertMethod(
+                    """
+                @Insert(onConflict=OnConflictStrategy.${pair.first})
+                abstract public void foo(User user);
+                """) { insertion, invocation ->
+                assertThat(insertion.onConflict, `is`(pair.second))
+            }.compilesWithoutError()
+        }
+    }
+
+    @Test
+    fun invalidReturnType() {
+        singleInsertMethod(
+                """
+                @Insert
+                abstract public int foo(User user);
+                """) { insertion, invocation ->
+            assertThat(insertion.insertionType, `is`(nullValue()))
+        }.failsToCompile().withErrorContaining(
+                ProcessorErrors.INVALID_INSERTION_METHOD_RETURN_TYPE)
+    }
+
+    @Test
+    fun mismatchedReturnType() {
+        singleInsertMethod(
+                """
+                @Insert
+                abstract public long[] foo(User user);
+                """) { insertion, invocation ->
+            assertThat(insertion.insertionType, `is`(nullValue()))
+        }.failsToCompile().withErrorContaining(
+                ProcessorErrors.insertionMethodReturnTypeMismatch(
+                        ArrayTypeName.of(TypeName.LONG),
+                        InsertionMethodProcessor.SINGLE_ITEM_SET.map { it.returnTypeName }))
+    }
+
+    @Test
+    fun mismatchedReturnType2() {
+        singleInsertMethod(
+                """
+                @Insert
+                abstract public long foo(User... user);
+                """) { insertion, invocation ->
+            assertThat(insertion.insertionType, `is`(nullValue()))
+        }.failsToCompile().withErrorContaining(
+                ProcessorErrors.insertionMethodReturnTypeMismatch(
+                        TypeName.LONG,
+                        InsertionMethodProcessor.MULTIPLE_ITEM_SET.map { it.returnTypeName }))
+    }
+
+    @Test
+    fun mismatchedReturnType3() {
+        singleInsertMethod(
+                """
+                @Insert
+                abstract public long foo(User user1, User user2);
+                """) { insertion, invocation ->
+            assertThat(insertion.insertionType, `is`(nullValue()))
+        }.failsToCompile().withErrorContaining(
+                ProcessorErrors.insertionMethodReturnTypeMismatch(
+                        TypeName.LONG,
+                        InsertionMethodProcessor.VOID_SET.map { it.returnTypeName }))
+    }
+
+    @Test
+    fun validReturnTypes() {
+        listOf(
+                Pair("void", Type.INSERT_VOID),
+                Pair("long", Type.INSERT_SINGLE_ID),
+                Pair("long[]", Type.INSERT_ID_ARRAY),
+                Pair("Long[]", Type.INSERT_ID_ARRAY_BOX),
+                Pair("List<Long>", Type.INSERT_ID_LIST)
+        ).forEach { pair ->
+            val dots = if (pair.second in setOf(Type.INSERT_ID_LIST, Type.INSERT_ID_ARRAY,
+                    Type.INSERT_ID_ARRAY_BOX)) {
+                "..."
+            } else {
+                ""
+            }
+            singleInsertMethod(
+                    """
+                @Insert
+                abstract public ${pair.first} foo(User$dots user);
+                """) { insertion, invocation ->
+                assertThat(insertion.insertMethodTypeFor(insertion.parameters.first()),
+                        `is`(pair.second))
+                assertThat(pair.toString(), insertion.insertionType, `is`(pair.second))
+            }.compilesWithoutError()
+        }
+    }
+
+    fun singleInsertMethod(vararg input: String,
+                          handler: (InsertionMethod, TestInvocation) -> Unit):
+            CompileTester {
+        return assertAbout(JavaSourcesSubjectFactory.javaSources())
+                .that(listOf(JavaFileObjects.forSourceString("foo.bar.MyClass",
+                        DAO_PREFIX + input.joinToString("\n") + DAO_SUFFIX
+                ), COMMON.USER, COMMON.BOOK, COMMON.NOT_AN_ENTITY))
+                .processedWith(TestProcessor.builder()
+                        .forAnnotations(Insert::class, Dao::class)
+                        .nextRunHandler { invocation ->
+                            val (owner, methods) = invocation.roundEnv
+                                    .getElementsAnnotatedWith(Dao::class.java)
+                                    .map {
+                                        Pair(it,
+                                                invocation.processingEnv.elementUtils
+                                                        .getAllMembers(MoreElements.asType(it))
+                                                        .filter {
+                                                            MoreElements.isAnnotationPresent(it,
+                                                                    Insert::class.java)
+                                                        }
+                                        )
+                                    }.filter { it.second.isNotEmpty() }.first()
+                            val processor = InsertionMethodProcessor(
+                                    baseContext = invocation.context,
+                                    containing = MoreTypes.asDeclared(owner.asType()),
+                                    executableElement = MoreElements.asExecutable(methods.first()))
+                            val processed = processor.process()
+                            handler(processed, invocation)
+                            true
+                        }
+                        .build())
+    }
+}
diff --git a/room/compiler/src/test/kotlin/android/arch/persistence/room/processor/PojoProcessorTest.kt b/room/compiler/src/test/kotlin/android/arch/persistence/room/processor/PojoProcessorTest.kt
new file mode 100644
index 0000000..b21903c
--- /dev/null
+++ b/room/compiler/src/test/kotlin/android/arch/persistence/room/processor/PojoProcessorTest.kt
@@ -0,0 +1,636 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.arch.persistence.room.processor
+
+import COMMON
+import android.arch.persistence.room.parser.SQLTypeAffinity
+import android.arch.persistence.room.processor.ProcessorErrors.CANNOT_FIND_TYPE
+import android.arch.persistence.room.processor.ProcessorErrors.ENTITY_MUST_BE_ANNOTATED_WITH_ENTITY
+import android.arch.persistence.room.processor.ProcessorErrors.POJO_FIELD_HAS_DUPLICATE_COLUMN_NAME
+import android.arch.persistence.room.processor.ProcessorErrors.RELATION_NOT_COLLECTION
+import android.arch.persistence.room.processor.ProcessorErrors.relationCannotFindEntityField
+import android.arch.persistence.room.processor.ProcessorErrors.relationCannotFindParentEntityField
+import android.arch.persistence.room.testing.TestInvocation
+import android.arch.persistence.room.vo.CallType
+import android.arch.persistence.room.vo.Constructor
+import android.arch.persistence.room.vo.EmbeddedField
+import android.arch.persistence.room.vo.Field
+import android.arch.persistence.room.vo.Pojo
+import android.arch.persistence.room.vo.RelationCollector
+import com.google.testing.compile.CompileTester
+import com.google.testing.compile.JavaFileObjects
+import com.squareup.javapoet.ClassName
+import com.squareup.javapoet.TypeName
+import org.hamcrest.CoreMatchers.`is`
+import org.hamcrest.CoreMatchers.instanceOf
+import org.hamcrest.CoreMatchers.not
+import org.hamcrest.CoreMatchers.notNullValue
+import org.hamcrest.CoreMatchers.nullValue
+import org.hamcrest.CoreMatchers.sameInstance
+import org.hamcrest.MatcherAssert.assertThat
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.junit.runners.JUnit4
+import org.mockito.Mockito.mock
+import simpleRun
+import javax.lang.model.element.Element
+import javax.tools.JavaFileObject
+
+/**
+ * Some of the functionality is tested via EntityProcessor.
+ */
+@RunWith(JUnit4::class)
+class PojoProcessorTest {
+
+    companion object {
+        val MY_POJO = ClassName.get("foo.bar", "MyPojo")
+        val HEADER = """
+            package foo.bar;
+            import android.arch.persistence.room.*;
+            import java.util.*;
+            public class MyPojo {
+            """
+        val FOOTER = "\n}"
+    }
+
+    private fun String.toJFO(qName: String) = JavaFileObjects.forSourceLines(qName, this)
+
+    @Test
+    fun inheritedPrivate() {
+        val parent = """
+            package foo.bar.x;
+            import android.arch.persistence.room.*;
+            public class BaseClass {
+                private String baseField;
+                public String getBaseField(){ return baseField; }
+                public void setBaseField(String baseField){ }
+            }
+        """
+        simpleRun(
+                """
+                package foo.bar;
+                import android.arch.persistence.room.*;
+                public class ${MY_POJO.simpleName()} extends foo.bar.x.BaseClass {
+                    public String myField;
+                }
+                """.toJFO(MY_POJO.toString()),
+                parent.toJFO("foo.bar.x.BaseClass")) { invocation ->
+            val pojo = PojoProcessor(baseContext = invocation.context,
+                    element = invocation.typeElement(MY_POJO.toString()),
+                    bindingScope = FieldProcessor.BindingScope.READ_FROM_CURSOR,
+                    parent = null).process()
+            assertThat(pojo.fields.find { it.name == "myField" }, notNullValue())
+            assertThat(pojo.fields.find { it.name == "baseField" }, notNullValue())
+        }.compilesWithoutError()
+    }
+
+    @Test
+    fun embedded() {
+        singleRun(
+                """
+                int id;
+                @Embedded
+                Point myPoint;
+                static class Point {
+                    int x;
+                    int y;
+                }
+                """
+        ) { pojo ->
+            assertThat(pojo.fields.size, `is`(3))
+            assertThat(pojo.fields[1].name, `is`("x"))
+            assertThat(pojo.fields[2].name, `is`("y"))
+            assertThat(pojo.fields[0].parent, nullValue())
+            assertThat(pojo.fields[1].parent, notNullValue())
+            assertThat(pojo.fields[2].parent, notNullValue())
+            val parent = pojo.fields[2].parent!!
+            assertThat(parent.prefix, `is`(""))
+            assertThat(parent.field.name, `is`("myPoint"))
+            assertThat(parent.pojo.typeName,
+                    `is`(ClassName.get("foo.bar.MyPojo", "Point") as TypeName))
+        }.compilesWithoutError()
+    }
+
+    @Test
+    fun embeddedWithPrefix() {
+        singleRun(
+                """
+                int id;
+                @Embedded(prefix = "foo")
+                Point myPoint;
+                static class Point {
+                    int x;
+                    @ColumnInfo(name = "y2")
+                    int y;
+                }
+                """
+        ) { pojo ->
+            assertThat(pojo.fields.size, `is`(3))
+            assertThat(pojo.fields[1].name, `is`("x"))
+            assertThat(pojo.fields[2].name, `is`("y"))
+            assertThat(pojo.fields[1].columnName, `is`("foox"))
+            assertThat(pojo.fields[2].columnName, `is`("fooy2"))
+            val parent = pojo.fields[2].parent!!
+            assertThat(parent.prefix, `is`("foo"))
+        }.compilesWithoutError()
+    }
+
+    @Test
+    fun nestedEmbedded() {
+        singleRun(
+                """
+                int id;
+                @Embedded(prefix = "foo")
+                Point myPoint;
+                static class Point {
+                    int x;
+                    @ColumnInfo(name = "y2")
+                    int y;
+                    @Embedded(prefix = "bar")
+                    Coordinate coordinate;
+                }
+                static class Coordinate {
+                    double lat;
+                    double lng;
+                    @Ignore
+                    String ignored;
+                }
+                """
+        ) { pojo ->
+            assertThat(pojo.fields.size, `is`(5))
+            assertThat(pojo.fields.map { it.columnName }, `is`(
+                    listOf("id", "foox", "fooy2", "foobarlat", "foobarlng")))
+        }.compilesWithoutError()
+    }
+
+    @Test
+    fun duplicateColumnNames() {
+        singleRun(
+                """
+                int id;
+                @ColumnInfo(name = "id")
+                int another;
+                """
+        ) { pojo ->
+        }.failsToCompile().withErrorContaining(
+                ProcessorErrors.pojoDuplicateFieldNames("id", listOf("id", "another"))
+        ).and().withErrorContaining(
+                POJO_FIELD_HAS_DUPLICATE_COLUMN_NAME
+        ).and().withErrorCount(3)
+    }
+
+    @Test
+    fun duplicateColumnNamesFromEmbedded() {
+        singleRun(
+                """
+                int id;
+                @Embedded
+                Foo foo;
+                static class Foo {
+                    @ColumnInfo(name = "id")
+                    int x;
+                }
+                """
+        ) { pojo ->
+        }.failsToCompile().withErrorContaining(
+                ProcessorErrors.pojoDuplicateFieldNames("id", listOf("id", "foo > x"))
+        ).and().withErrorContaining(
+                POJO_FIELD_HAS_DUPLICATE_COLUMN_NAME
+        ).and().withErrorCount(3)
+    }
+
+    @Test
+    fun dropSubPrimaryKeyNoWarningForPojo() {
+        singleRun(
+                """
+                @PrimaryKey
+                int id;
+                @Embedded
+                Point myPoint;
+                static class Point {
+                    @PrimaryKey
+                    int x;
+                    int y;
+                }
+                """
+        ) { pojo ->
+        }.compilesWithoutError().withWarningCount(0)
+    }
+
+    @Test
+    fun relation_notCollection() {
+        singleRun(
+                """
+                int id;
+                @Relation(parentColumn = "id", entityColumn = "uid")
+                public User user;
+                """, COMMON.USER
+        ) { pojo ->
+        }.failsToCompile().withErrorContaining(RELATION_NOT_COLLECTION)
+    }
+
+    @Test
+    fun relation_columnInfo() {
+        singleRun(
+                """
+                int id;
+                @ColumnInfo
+                @Relation(parentColumn = "id", entityColumn = "uid")
+                public List<User> user;
+                """, COMMON.USER
+        ) { pojo ->
+        }.failsToCompile().withErrorContaining(
+                ProcessorErrors.CANNOT_USE_MORE_THAN_ONE_POJO_FIELD_ANNOTATION)
+    }
+
+    @Test
+    fun relation_notEntity() {
+        singleRun(
+                """
+                int id;
+                @Relation(parentColumn = "id", entityColumn = "uid")
+                public List<NotAnEntity> user;
+                """, COMMON.NOT_AN_ENTITY
+        ) { pojo ->
+        }.failsToCompile().withErrorContaining(ENTITY_MUST_BE_ANNOTATED_WITH_ENTITY)
+    }
+
+    @Test
+    fun relation_missingParent() {
+        singleRun(
+                """
+                int id;
+                @Relation(parentColumn = "idk", entityColumn = "uid")
+                public List<User> user;
+                """, COMMON.USER
+        ) { pojo ->
+        }.failsToCompile().withErrorContaining(
+                relationCannotFindParentEntityField("foo.bar.MyPojo", "idk", listOf("id"))
+        )
+    }
+
+    @Test
+    fun relation_missingEntityField() {
+        singleRun(
+                """
+                int id;
+                @Relation(parentColumn = "id", entityColumn = "idk")
+                public List<User> user;
+                """, COMMON.USER
+        ) { pojo ->
+        }.failsToCompile().withErrorContaining(
+                relationCannotFindEntityField("foo.bar.User", "idk",
+                        listOf("uid", "name", "lastName", "age"))
+        )
+    }
+
+    @Test
+    fun relation_missingType() {
+        singleRun(
+                """
+                int id;
+                @Relation(parentColumn = "id", entityColumn = "uid")
+                public List<User> user;
+                """
+        ) { pojo ->
+        }.failsToCompile().withErrorContaining(CANNOT_FIND_TYPE)
+    }
+
+    @Test
+    fun relation_nestedField() {
+        singleRun(
+                """
+                static class Nested {
+                    @ColumnInfo(name = "foo")
+                    public int id;
+                }
+                @Embedded
+                Nested nested;
+                @Relation(parentColumn = "foo", entityColumn = "uid")
+                public List<User> user;
+                """, COMMON.USER
+        ) { pojo ->
+            assertThat(pojo.relations.first().parentField.columnName, `is`("foo"))
+        }.compilesWithoutError()
+    }
+
+    @Test
+    fun relation_nestedRelation() {
+        singleRun(
+                """
+                static class UserWithNested {
+                    @Embedded
+                    public User user;
+                    @Relation(parentColumn = "uid", entityColumn = "uid")
+                    public List<User> selfs;
+                }
+                int id;
+                @Relation(parentColumn = "id", entityColumn = "uid", entity = User.class)
+                public List<UserWithNested> user;
+                """, COMMON.USER
+        ) { pojo, invocation ->
+            assertThat(pojo.relations.first().parentField.name, `is`("id"))
+        }.compilesWithoutError().withWarningCount(0)
+    }
+
+    @Test
+    fun relation_affinityMismatch() {
+        singleRun(
+                """
+                String id;
+                @Relation(parentColumn = "id", entityColumn = "uid")
+                public List<User> user;
+                """, COMMON.USER
+        ) { pojo, invocation ->
+            // trigger assignment evaluation
+            RelationCollector.createCollectors(invocation.context, pojo.relations)
+            assertThat(pojo.relations.size, `is`(1))
+            assertThat(pojo.relations.first().entityField.name, `is`("uid"))
+            assertThat(pojo.relations.first().parentField.name, `is`("id"))
+        }.compilesWithoutError().withWarningContaining(
+                ProcessorErrors.relationAffinityMismatch(
+                        parentAffinity = SQLTypeAffinity.TEXT,
+                        childAffinity = SQLTypeAffinity.INTEGER,
+                        parentColumn = "id",
+                        childColumn = "uid")
+        )
+    }
+
+    @Test
+    fun relation_simple() {
+        singleRun(
+                """
+                int id;
+                @Relation(parentColumn = "id", entityColumn = "uid")
+                public List<User> user;
+                """, COMMON.USER
+        ) { pojo ->
+            assertThat(pojo.relations.size, `is`(1))
+            assertThat(pojo.relations.first().entityField.name, `is`("uid"))
+            assertThat(pojo.relations.first().parentField.name, `is`("id"))
+        }.compilesWithoutError().withWarningCount(0)
+    }
+
+    @Test
+    fun relation_badProjection() {
+        singleRun(
+                """
+                int id;
+                @Relation(parentColumn = "id", entityColumn = "uid", projection={"i_dont_exist"})
+                public List<User> user;
+                """, COMMON.USER
+        ) { pojo ->
+        }.failsToCompile().withErrorContaining(
+                ProcessorErrors.relationBadProject("foo.bar.User", listOf("i_dont_exist"),
+                        listOf("uid", "name", "lastName", "ageColumn"))
+        )
+    }
+
+    @Test
+    fun cache() {
+        val pojo = """
+            $HEADER
+            int id;
+            $FOOTER
+            """.toJFO(MY_POJO.toString())
+        simpleRun(pojo) { invocation ->
+            val element = invocation.typeElement(MY_POJO.toString())
+            val pojo1 = PojoProcessor(invocation.context, element,
+                    FieldProcessor.BindingScope.BIND_TO_STMT, null).process()
+            assertThat(pojo1, notNullValue())
+            val pojo2 = PojoProcessor(invocation.context, element,
+                    FieldProcessor.BindingScope.BIND_TO_STMT, null).process()
+            assertThat(pojo2, sameInstance(pojo1))
+
+            val pojo3 = PojoProcessor(invocation.context, element,
+                    FieldProcessor.BindingScope.READ_FROM_CURSOR, null).process()
+            assertThat(pojo3, notNullValue())
+            assertThat(pojo3, not(sameInstance(pojo1)))
+
+            val pojo4 = PojoProcessor(invocation.context, element,
+                    FieldProcessor.BindingScope.TWO_WAY, null).process()
+            assertThat(pojo4, notNullValue())
+            assertThat(pojo4, not(sameInstance(pojo1)))
+            assertThat(pojo4, not(sameInstance(pojo3)))
+
+            val pojo5 = PojoProcessor(invocation.context, element,
+                    FieldProcessor.BindingScope.TWO_WAY, null).process()
+            assertThat(pojo5, sameInstance(pojo4))
+
+            val fakeField = Field(
+                    element = mock(Element::class.java),
+                    name = "foo",
+                    type = invocation.context.COMMON_TYPES.STRING,
+                    affinity = SQLTypeAffinity.TEXT,
+                    columnName = "foo",
+                    parent = null,
+                    indexed =  false
+            )
+            val fakeEmbedded = EmbeddedField(fakeField, "", null)
+
+            val pojo6 = PojoProcessor(invocation.context, element,
+                    FieldProcessor.BindingScope.TWO_WAY, fakeEmbedded).process()
+            assertThat(pojo6, notNullValue())
+            assertThat(pojo6, not(sameInstance(pojo1)))
+            assertThat(pojo6, not(sameInstance(pojo3)))
+            assertThat(pojo6, not(sameInstance(pojo4)))
+
+            val pojo7 = PojoProcessor(invocation.context, element,
+                    FieldProcessor.BindingScope.TWO_WAY, fakeEmbedded).process()
+            assertThat(pojo7, sameInstance(pojo6))
+        }.compilesWithoutError()
+    }
+
+    @Test
+    fun constructor_empty() {
+        val pojo = """
+            public String mName;
+            """
+        singleRun(pojo) { pojo ->
+            assertThat(pojo.constructor, notNullValue())
+            assertThat(pojo.constructor?.params, `is`(emptyList<Constructor.Param>()))
+        }.compilesWithoutError()
+    }
+
+    @Test
+    fun constructor_ambiguous_twoFieldsExcatMatch() {
+        val pojo = """
+            public String mName;
+            public String _name;
+            public MyPojo(String mName) {
+            }
+            """
+        singleRun(pojo) { pojo ->
+            val param = pojo.constructor?.params?.first()
+            assertThat(param, instanceOf(Constructor.FieldParam::class.java))
+            assertThat((param as Constructor.FieldParam).field.name,  `is`("mName"))
+            assertThat(pojo.fields.find { it.name == "mName" }?.setter?.callType,
+                    `is`(CallType.CONSTRUCTOR))
+        }.compilesWithoutError()
+    }
+
+    @Test
+    fun constructor_ambiguous_oneTypeMatches() {
+        val pojo = """
+            public String mName;
+            public int _name;
+            public MyPojo(String name) {
+            }
+            """
+        singleRun(pojo) { pojo ->
+            val param = pojo.constructor?.params?.first()
+            assertThat(param, instanceOf(Constructor.FieldParam::class.java))
+            assertThat((param as Constructor.FieldParam).field.name,  `is`("mName"))
+            assertThat(pojo.fields.find { it.name == "mName" }?.setter?.callType,
+                    `is`(CallType.CONSTRUCTOR))
+        }.compilesWithoutError()
+    }
+
+    @Test
+    fun constructor_ambiguous_twoFields() {
+        val pojo = """
+            String mName;
+            String _name;
+            public MyPojo(String name) {
+            }
+            """
+        singleRun(pojo) { pojo ->
+        }.failsToCompile().withErrorContaining(
+                ProcessorErrors.ambigiousConstructor(MY_POJO.toString(),
+                        "name", listOf("mName", "_name"))
+        )
+    }
+
+    @Test
+    fun constructor_noMatchBadType() {
+        singleRun("""
+            int foo;
+            public MyPojo(String foo) {
+            }
+        """) { pojo ->
+        }.failsToCompile().withErrorContaining(ProcessorErrors.MISSING_POJO_CONSTRUCTOR)
+    }
+
+    @Test
+    fun constructor_noMatch() {
+        singleRun("""
+            String mName;
+            String _name;
+            public MyPojo(String foo) {
+            }
+        """) { pojo ->
+        }.failsToCompile().withErrorContaining(ProcessorErrors.MISSING_POJO_CONSTRUCTOR)
+    }
+
+    @Test
+    fun constructor_noMatchMultiArg() {
+        singleRun("""
+            String mName;
+            int bar;
+            public MyPojo(String foo, String name) {
+            }
+        """) { pojo ->
+        }.failsToCompile().withErrorContaining(ProcessorErrors.MISSING_POJO_CONSTRUCTOR)
+    }
+
+    @Test
+    fun constructor_multipleMatching() {
+        singleRun("""
+            String mName;
+            String mLastName;
+            public MyPojo(String name) {
+            }
+            public MyPojo(String name, String lastName) {
+            }
+        """) { pojo ->
+        }.failsToCompile().withErrorContaining(ProcessorErrors.TOO_MANY_POJO_CONSTRUCTORS)
+    }
+
+    @Test
+    fun constructor_multipleMatchingWithIgnored() {
+        singleRun("""
+            String mName;
+            String mLastName;
+            @Ignore
+            public MyPojo(String name) {
+            }
+            public MyPojo(String name, String lastName) {
+            }
+        """) { pojo ->
+            assertThat(pojo.constructor, notNullValue())
+            assertThat(pojo.constructor?.params?.size, `is`(2))
+            assertThat(pojo.fields.find { it.name == "mName" }?.setter?.callType,
+                    `is`(CallType.CONSTRUCTOR))
+            assertThat(pojo.fields.find { it.name == "mLastName" }?.setter?.callType,
+                    `is`(CallType.CONSTRUCTOR))
+        }.compilesWithoutError()
+    }
+
+    @Test
+    fun constructor_dontTryForBindToScope() {
+        singleRun("""
+            String mName;
+            String mLastName;
+        """) { pojo, invocation ->
+            val process2 = PojoProcessor(baseContext = invocation.context,
+                    element = invocation.typeElement(MY_POJO.toString()),
+                    bindingScope = FieldProcessor.BindingScope.BIND_TO_STMT,
+                    parent = null).process()
+            assertThat(process2.constructor, nullValue())
+        }.compilesWithoutError()
+    }
+
+    @Test
+    fun constructor_bindForTwoWay() {
+        singleRun("""
+            String mName;
+            String mLastName;
+        """) { pojo, invocation ->
+            val process2 = PojoProcessor(baseContext = invocation.context,
+                    element = invocation.typeElement(MY_POJO.toString()),
+                    bindingScope = FieldProcessor.BindingScope.TWO_WAY,
+                    parent = null).process()
+            assertThat(process2.constructor, notNullValue())
+        }.compilesWithoutError()
+    }
+
+    fun singleRun(code: String, vararg jfos:JavaFileObject, handler: (Pojo) -> Unit)
+            : CompileTester {
+        return singleRun(code, *jfos) { pojo, invocation ->
+            handler(pojo)
+        }
+    }
+
+    fun singleRun(code: String, vararg jfos:JavaFileObject,
+                  handler: (Pojo, TestInvocation) -> Unit): CompileTester {
+        val pojoJFO = """
+                $HEADER
+                $code
+                $FOOTER
+                """.toJFO(MY_POJO.toString())
+        val all = (jfos.toList() + pojoJFO).toTypedArray()
+        return simpleRun(*all) { invocation ->
+            handler.invoke(
+                    PojoProcessor(baseContext = invocation.context,
+                            element = invocation.typeElement(MY_POJO.toString()),
+                            bindingScope = FieldProcessor.BindingScope.READ_FROM_CURSOR,
+                            parent = null).process(),
+                    invocation
+            )
+        }
+    }
+}
diff --git a/room/compiler/src/test/kotlin/android/arch/persistence/room/processor/QueryMethodProcessorTest.kt b/room/compiler/src/test/kotlin/android/arch/persistence/room/processor/QueryMethodProcessorTest.kt
new file mode 100644
index 0000000..3474441
--- /dev/null
+++ b/room/compiler/src/test/kotlin/android/arch/persistence/room/processor/QueryMethodProcessorTest.kt
@@ -0,0 +1,646 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.arch.persistence.room.processor
+
+import COMMON
+import android.arch.persistence.room.ColumnInfo
+import android.arch.persistence.room.Dao
+import android.arch.persistence.room.Entity
+import android.arch.persistence.room.PrimaryKey
+import android.arch.persistence.room.Query
+import android.arch.persistence.room.ext.LifecyclesTypeNames
+import android.arch.persistence.room.ext.hasAnnotation
+import android.arch.persistence.room.ext.typeName
+import android.arch.persistence.room.processor.ProcessorErrors.CANNOT_FIND_QUERY_RESULT_ADAPTER
+import android.arch.persistence.room.solver.query.result.LiveDataQueryResultBinder
+import android.arch.persistence.room.solver.query.result.PojoRowAdapter
+import android.arch.persistence.room.solver.query.result.SingleEntityQueryResultAdapter
+import android.arch.persistence.room.testing.TestInvocation
+import android.arch.persistence.room.testing.TestProcessor
+import android.arch.persistence.room.vo.Field
+import android.arch.persistence.room.vo.QueryMethod
+import android.arch.persistence.room.vo.Warning
+import com.google.auto.common.MoreElements
+import com.google.auto.common.MoreTypes
+import com.google.common.truth.Truth.assertAbout
+import com.google.testing.compile.CompileTester
+import com.google.testing.compile.JavaFileObjects
+import com.google.testing.compile.JavaSourcesSubjectFactory
+import com.squareup.javapoet.ArrayTypeName
+import com.squareup.javapoet.ClassName
+import com.squareup.javapoet.ParameterizedTypeName
+import com.squareup.javapoet.TypeName
+import com.squareup.javapoet.TypeVariableName
+import createVerifierFromEntities
+import org.hamcrest.CoreMatchers.`is`
+import org.hamcrest.CoreMatchers.instanceOf
+import org.hamcrest.CoreMatchers.notNullValue
+import org.hamcrest.CoreMatchers.nullValue
+import org.hamcrest.MatcherAssert.assertThat
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.junit.runners.Parameterized
+import org.mockito.Mockito
+import javax.lang.model.element.Element
+import javax.lang.model.type.DeclaredType
+import javax.lang.model.type.TypeKind.INT
+import javax.lang.model.type.TypeMirror
+
+@Suppress("PLATFORM_CLASS_MAPPED_TO_KOTLIN")
+@RunWith(Parameterized::class)
+class QueryMethodProcessorTest(val enableVerification: Boolean) {
+    companion object {
+        const val DAO_PREFIX = """
+                package foo.bar;
+                import android.arch.persistence.room.*;
+                @Dao
+                abstract class MyClass {
+                """
+        const val DAO_SUFFIX = "}"
+        val POJO = ClassName.get("foo.bar", "MyClass.Pojo")
+        @Parameterized.Parameters(name = "enableDbVerification={0}")
+        @JvmStatic
+        fun getParams() = arrayOf(true, false)
+
+        fun createField(name: String, columnName: String? = null): Field {
+            return Field(
+                    element = Mockito.mock(Element::class.java),
+                    name = name,
+                    type = Mockito.mock(TypeMirror::class.java),
+                    columnName = columnName ?: name,
+                    affinity = null
+            )
+        }
+    }
+
+    @Test
+    fun testReadNoParams() {
+        singleQueryMethod(
+                """
+                @Query("SELECT * from User")
+                abstract public int[] foo();
+                """) { parsedQuery, invocation ->
+            assertThat(parsedQuery.name, `is`("foo"))
+            assertThat(parsedQuery.parameters.size, `is`(0))
+            assertThat(parsedQuery.returnType.typeName(),
+                    `is`(ArrayTypeName.of(TypeName.INT) as TypeName))
+        }.compilesWithoutError()
+    }
+
+    @Test
+    fun testSingleParam() {
+        singleQueryMethod(
+                """
+                @Query("SELECT * from User where uid = :x")
+                abstract public long foo(int x);
+                """) { parsedQuery, invocation ->
+            assertThat(parsedQuery.name, `is`("foo"))
+            assertThat(parsedQuery.returnType.typeName(), `is`(TypeName.LONG))
+            assertThat(parsedQuery.parameters.size, `is`(1))
+            val param = parsedQuery.parameters.first()
+            assertThat(param.name, `is`("x"))
+            assertThat(param.type,
+                    `is`(invocation.processingEnv.typeUtils.getPrimitiveType(INT) as TypeMirror))
+        }.compilesWithoutError()
+    }
+
+    @Test
+    fun testVarArgs() {
+        singleQueryMethod(
+                """
+                @Query("SELECT * from User where uid in (:ids)")
+                abstract public long foo(int... ids);
+                """) { parsedQuery, invocation ->
+            assertThat(parsedQuery.name, `is`("foo"))
+            assertThat(parsedQuery.returnType.typeName(), `is`(TypeName.LONG))
+            assertThat(parsedQuery.parameters.size, `is`(1))
+            val param = parsedQuery.parameters.first()
+            assertThat(param.name, `is`("ids"))
+            val types = invocation.processingEnv.typeUtils
+            assertThat(param.type,
+                    `is`(types.getArrayType(types.getPrimitiveType(INT)) as TypeMirror))
+        }.compilesWithoutError()
+    }
+
+    @Test
+    fun testParamBindingMatchingNoName() {
+        singleQueryMethod(
+                """
+                @Query("SELECT uid from User where uid = :id")
+                abstract public long getIdById(int id);
+                """) { parsedQuery, invocation ->
+            val section = parsedQuery.query.bindSections.first()
+            val param = parsedQuery.parameters.firstOrNull()
+            assertThat(section, notNullValue())
+            assertThat(param, notNullValue())
+            assertThat(parsedQuery.sectionToParamMapping, `is`(listOf(Pair(section, param))))
+        }.compilesWithoutError()
+    }
+
+    @Test
+    fun testParamBindingMatchingSimpleBind() {
+        singleQueryMethod(
+                """
+                @Query("SELECT uid from User where uid = :id")
+                abstract public long getIdById(int id);
+                """) { parsedQuery, invocation ->
+            val section = parsedQuery.query.bindSections.first()
+            val param = parsedQuery.parameters.firstOrNull()
+            assertThat(section, notNullValue())
+            assertThat(param, notNullValue())
+            assertThat(parsedQuery.sectionToParamMapping,
+                    `is`(listOf(Pair(section, param))))
+        }.compilesWithoutError()
+    }
+
+    @Test
+    fun testParamBindingTwoBindVarsIntoTheSameParameter() {
+        singleQueryMethod(
+                """
+                @Query("SELECT uid from User where uid = :id OR uid = :id")
+                abstract public long getIdById(int id);
+                """) { parsedQuery, invocation ->
+            val section = parsedQuery.query.bindSections[0]
+            val section2 = parsedQuery.query.bindSections[1]
+            val param = parsedQuery.parameters.firstOrNull()
+            assertThat(section, notNullValue())
+            assertThat(section2, notNullValue())
+            assertThat(param, notNullValue())
+            assertThat(parsedQuery.sectionToParamMapping,
+                    `is`(listOf(Pair(section, param), Pair(section2, param))))
+        }.compilesWithoutError()
+    }
+
+    @Test
+    fun testMissingParameterForBinding() {
+        singleQueryMethod(
+                """
+                @Query("SELECT uid from User where uid = :id OR uid = :uid")
+                abstract public long getIdById(int id);
+                """) { parsedQuery, invocation ->
+            val section = parsedQuery.query.bindSections[0]
+            val section2 = parsedQuery.query.bindSections[1]
+            val param = parsedQuery.parameters.firstOrNull()
+            assertThat(section, notNullValue())
+            assertThat(section2, notNullValue())
+            assertThat(param, notNullValue())
+            assertThat(parsedQuery.sectionToParamMapping,
+                    `is`(listOf(Pair(section, param), Pair(section2, null))))
+        }
+                .failsToCompile()
+                .withErrorContaining(
+                        ProcessorErrors.missingParameterForBindVariable(listOf(":uid")))
+    }
+
+    @Test
+    fun test2MissingParameterForBinding() {
+        singleQueryMethod(
+                """
+                @Query("SELECT uid from User where name = :bar AND uid = :id OR uid = :uid")
+                abstract public long getIdById(int id);
+                """) { parsedQuery, invocation ->
+            val bar = parsedQuery.query.bindSections[0]
+            val id = parsedQuery.query.bindSections[1]
+            val uid = parsedQuery.query.bindSections[2]
+            val param = parsedQuery.parameters.firstOrNull()
+            assertThat(bar, notNullValue())
+            assertThat(id, notNullValue())
+            assertThat(uid, notNullValue())
+            assertThat(param, notNullValue())
+            assertThat(parsedQuery.sectionToParamMapping,
+                    `is`(listOf(Pair(bar, null), Pair(id, param), Pair(uid, null))))
+        }
+                .failsToCompile()
+                .withErrorContaining(
+                        ProcessorErrors.missingParameterForBindVariable(listOf(":bar", ":uid")))
+    }
+
+    @Test
+    fun testUnusedParameters() {
+        singleQueryMethod(
+                """
+                @Query("SELECT uid from User where name = :bar")
+                abstract public long getIdById(int bar, int whyNotUseMe);
+                """) { parsedQuery, invocation ->
+            val bar = parsedQuery.query.bindSections[0]
+            val barParam = parsedQuery.parameters.firstOrNull()
+            assertThat(bar, notNullValue())
+            assertThat(barParam, notNullValue())
+            assertThat(parsedQuery.sectionToParamMapping,
+                    `is`(listOf(Pair(bar, barParam))))
+        }.failsToCompile().withErrorContaining(
+                ProcessorErrors.unusedQueryMethodParameter(listOf("whyNotUseMe")))
+    }
+
+    @Test
+    fun testNameWithUnderscore() {
+        singleQueryMethod(
+                """
+                @Query("select * from User where uid = :_blah")
+                abstract public long getSth(int _blah);
+                """
+        ) { parsedQuery, invocation -> }
+                .failsToCompile()
+                .withErrorContaining(ProcessorErrors.QUERY_PARAMETERS_CANNOT_START_WITH_UNDERSCORE)
+    }
+
+    @Test
+    fun testGenericReturnType() {
+        singleQueryMethod(
+                """
+                @Query("select * from User")
+                abstract public <T> java.util.List<T> foo(int x);
+                """) { parsedQuery, invocation ->
+            val expected: TypeName = ParameterizedTypeName.get(ClassName.get(List::class.java),
+                    TypeVariableName.get("T"))
+            assertThat(parsedQuery.returnType.typeName(), `is`(expected))
+        }.failsToCompile()
+                .withErrorContaining(ProcessorErrors.CANNOT_USE_UNBOUND_GENERICS_IN_QUERY_METHODS)
+    }
+
+    @Test
+    fun testBadQuery() {
+        singleQueryMethod(
+                """
+                @Query("select * from :1 :2")
+                abstract public long foo(int x);
+                """) { parsedQuery, invocation ->
+            // do nothing
+        }.failsToCompile()
+                .withErrorContaining("UNEXPECTED_CHAR=:")
+    }
+
+    @Test
+    fun testBoundGeneric() {
+        singleQueryMethod(
+                """
+                static abstract class BaseModel<T> {
+                    @Query("select COUNT(*) from User")
+                    abstract public T getT();
+                }
+                @Dao
+                static abstract class ExtendingModel extends BaseModel<Integer> {
+                }
+                """) { parsedQuery, invocation ->
+            assertThat(parsedQuery.returnType.typeName(),
+                    `is`(ClassName.get(Integer::class.java) as TypeName))
+        }.compilesWithoutError()
+    }
+
+    @Test
+    fun testBoundGenericParameter() {
+        singleQueryMethod(
+                """
+                static abstract class BaseModel<T> {
+                    @Query("select COUNT(*) from User where :t")
+                    abstract public int getT(T t);
+                }
+                @Dao
+                static abstract class ExtendingModel extends BaseModel<Integer> {
+                }
+                """) { parsedQuery, invocation ->
+            assertThat(parsedQuery.parameters.first().type,
+                    `is`(invocation.processingEnv.elementUtils
+                            .getTypeElement("java.lang.Integer").asType()))
+        }.compilesWithoutError()
+    }
+
+    @Test
+    fun testReadDeleteWithBadReturnType() {
+        singleQueryMethod(
+                """
+                @Query("DELETE from User where uid = :id")
+                abstract public float foo(int id);
+                """) { parsedQuery, invocation ->
+        }.failsToCompile().withErrorContaining(
+                ProcessorErrors.DELETION_METHODS_MUST_RETURN_VOID_OR_INT
+        )
+    }
+
+    @Test
+    fun testSimpleDelete() {
+        singleQueryMethod(
+                """
+                @Query("DELETE from User where uid = :id")
+                abstract public int foo(int id);
+                """) { parsedQuery, invocation ->
+            assertThat(parsedQuery.name, `is`("foo"))
+            assertThat(parsedQuery.parameters.size, `is`(1))
+            assertThat(parsedQuery.returnType.typeName(), `is`(TypeName.INT))
+        }.compilesWithoutError()
+    }
+
+    @Test
+    fun testVoidDeleteQuery() {
+        singleQueryMethod(
+                """
+                @Query("DELETE from User where uid = :id")
+                abstract public void foo(int id);
+                """) { parsedQuery, invocation ->
+            assertThat(parsedQuery.name, `is`("foo"))
+            assertThat(parsedQuery.parameters.size, `is`(1))
+            assertThat(parsedQuery.returnType.typeName(), `is`(TypeName.VOID))
+        }.compilesWithoutError()
+    }
+
+    @Test
+    fun testVoidUpdateQuery() {
+        singleQueryMethod(
+                """
+                @Query("update user set name = :name")
+                abstract public void updateAllNames(String name);
+                """) { parsedQuery, invocation ->
+            assertThat(parsedQuery.name, `is`("updateAllNames"))
+            assertThat(parsedQuery.parameters.size, `is`(1))
+            assertThat(parsedQuery.returnType.typeName(), `is`(TypeName.VOID))
+            assertThat(parsedQuery.parameters.first().type.typeName(),
+                    `is`(invocation.context.COMMON_TYPES.STRING.typeName()))
+        }.compilesWithoutError()
+    }
+
+    @Test
+    fun testLiveDataQuery() {
+        singleQueryMethod(
+                """
+                @Query("select name from user where uid = :id")
+                abstract ${LifecyclesTypeNames.LIVE_DATA}<String> nameLiveData(String id);
+                """
+        ) { parsedQuery, invocation ->
+            assertThat(parsedQuery.returnType.typeName(),
+                    `is`(ParameterizedTypeName.get(LifecyclesTypeNames.LIVE_DATA,
+                            String::class.typeName()) as TypeName))
+            assertThat(parsedQuery.queryResultBinder,
+                    instanceOf(LiveDataQueryResultBinder::class.java))
+        }.compilesWithoutError()
+    }
+
+    @Test
+    fun testNonSelectLiveData() {
+        singleQueryMethod(
+                """
+                @Query("delete from user where uid = :id")
+                abstract ${LifecyclesTypeNames.LIVE_DATA}<Integer> deleteLiveData(String id);
+                """
+        ) { parsedQuery, invocation ->
+        }.failsToCompile()
+                .withErrorContaining(ProcessorErrors.DELETION_METHODS_MUST_RETURN_VOID_OR_INT)
+    }
+
+    @Test
+    fun skipVerification() {
+        singleQueryMethod(
+                """
+                @SkipQueryVerification
+                @Query("SELECT foo from User")
+                abstract public int[] foo();
+                """) { parsedQuery, invocation ->
+            assertThat(parsedQuery.name, `is`("foo"))
+            assertThat(parsedQuery.parameters.size, `is`(0))
+            assertThat(parsedQuery.returnType.typeName(),
+                    `is`(ArrayTypeName.of(TypeName.INT) as TypeName))
+        }.compilesWithoutError()
+    }
+
+    @Test
+    fun suppressWarnings() {
+        singleQueryMethod("""
+                @SuppressWarnings(RoomWarnings.CURSOR_MISMATCH)
+                @Query("SELECT uid from User")
+                abstract public int[] foo();
+                """) { method, invocation ->
+            assertThat(QueryMethodProcessor(
+                    baseContext = invocation.context,
+                    containing = Mockito.mock(DeclaredType::class.java),
+                    executableElement = method.element,
+                    dbVerifier = null).context.logger.suppressedWarnings
+                    , `is`(setOf(Warning.CURSOR_MISMATCH)))
+        }.compilesWithoutError()
+    }
+
+    @Test
+    fun pojo_renamedColumn() {
+        pojoTest("""
+                String name;
+                String lName;
+                """, listOf("name", "lastName as lName")) { adapter, queryMethod, invocation ->
+            assertThat(adapter?.mapping?.unusedColumns, `is`(emptyList()))
+            assertThat(adapter?.mapping?.unusedFields, `is`(emptyList()))
+        }?.compilesWithoutError()?.withWarningCount(0)
+    }
+
+    @Test
+    fun pojo_exactMatch() {
+        pojoTest("""
+                String name;
+                String lastName;
+                """, listOf("name", "lastName")) { adapter, queryMethod, invocation ->
+            assertThat(adapter?.mapping?.unusedColumns, `is`(emptyList()))
+            assertThat(adapter?.mapping?.unusedFields, `is`(emptyList()))
+        }?.compilesWithoutError()?.withWarningCount(0)
+    }
+
+    @Test
+    fun pojo_exactMatchWithStar() {
+        pojoTest("""
+            String name;
+            String lastName;
+            int uid;
+            @ColumnInfo(name = "ageColumn")
+            int age;
+        """, listOf("*")) { adapter, queryMethod, invocation ->
+            assertThat(adapter?.mapping?.unusedColumns, `is`(emptyList()))
+            assertThat(adapter?.mapping?.unusedFields, `is`(emptyList()))
+        }?.compilesWithoutError()?.withWarningCount(0)
+    }
+
+    @Test
+    fun pojo_nonJavaName() {
+        pojoTest("""
+            @ColumnInfo(name = "MAX(ageColumn)")
+            int maxAge;
+            String name;
+            """, listOf("MAX(ageColumn)", "name")) { adapter, queryMethod, invocation ->
+            assertThat(adapter?.mapping?.unusedColumns, `is`(emptyList()))
+            assertThat(adapter?.mapping?.unusedFields, `is`(emptyList()))
+        }?.compilesWithoutError()?.withWarningCount(0)
+    }
+
+    @Test
+    fun pojo_noMatchingFields() {
+        pojoTest("""
+                String nameX;
+                String lastNameX;
+                """, listOf("name", "lastName")) { adapter, queryMethod, invocation ->
+            assertThat(adapter?.mapping?.unusedColumns, `is`(listOf("name", "lastName")))
+            assertThat(adapter?.mapping?.unusedFields, `is`(adapter?.pojo?.fields))
+        }?.failsToCompile()
+                ?.withErrorContaining(CANNOT_FIND_QUERY_RESULT_ADAPTER)
+                ?.and()
+                ?.withWarningContaining(
+                        ProcessorErrors.cursorPojoMismatch(
+                                pojoTypeName = POJO,
+                                unusedColumns = listOf("name", "lastName"),
+                                unusedFields = listOf(createField("nameX"),
+                                        createField("lastNameX")),
+                                allColumns = listOf("name", "lastName"),
+                                allFields = listOf(createField("nameX"), createField("lastNameX"))
+                        )
+                )
+    }
+
+    @Test
+    fun pojo_badQuery() {
+        // do not report mismatch if query is broken
+        pojoTest("""
+            @ColumnInfo(name = "MAX(ageColumn)")
+            int maxAge;
+            String name;
+            """, listOf("MAX(age)", "name")) { adapter, queryMethod, invocation ->
+        }?.failsToCompile()
+                ?.withErrorContaining("no such column: age")
+                ?.and()
+                ?.withErrorCount(1)
+                ?.withWarningCount(0)
+    }
+
+    @Test
+    fun pojo_tooManyColumns() {
+        pojoTest("""
+            String name;
+            String lastName;
+            """, listOf("uid", "name", "lastName")) { adapter, queryMethod, invocation ->
+            assertThat(adapter?.mapping?.unusedColumns, `is`(listOf("uid")))
+            assertThat(adapter?.mapping?.unusedFields, `is`(emptyList()))
+        }?.compilesWithoutError()?.withWarningContaining(
+                ProcessorErrors.cursorPojoMismatch(
+                        pojoTypeName = POJO,
+                        unusedColumns = listOf("uid"),
+                        unusedFields = emptyList(),
+                        allColumns = listOf("uid", "name", "lastName"),
+                        allFields = listOf(createField("name"), createField("lastName"))
+                ))
+    }
+
+    @Test
+    fun pojo_tooManyFields() {
+        pojoTest("""
+            String name;
+            String lastName;
+            """, listOf("lastName")) { adapter, queryMethod, invocation ->
+            assertThat(adapter?.mapping?.unusedColumns, `is`(emptyList()))
+            assertThat(adapter?.mapping?.unusedFields, `is`(
+                    adapter?.pojo?.fields?.filter { it.name == "name" }
+            ))
+        }?.compilesWithoutError()?.withWarningContaining(
+                ProcessorErrors.cursorPojoMismatch(
+                        pojoTypeName = POJO,
+                        unusedColumns = emptyList(),
+                        unusedFields = listOf(createField("name")),
+                        allColumns = listOf("lastName"),
+                        allFields = listOf(createField("name"), createField("lastName"))
+                ))
+    }
+
+    @Test
+    fun pojo_tooManyFieldsAndColumns() {
+        pojoTest("""
+            String name;
+            String lastName;
+            """, listOf("uid", "name")) { adapter, queryMethod, invocation ->
+            assertThat(adapter?.mapping?.unusedColumns, `is`(listOf("uid")))
+            assertThat(adapter?.mapping?.unusedFields, `is`(
+                    adapter?.pojo?.fields?.filter { it.name == "lastName" }
+            ))
+        }?.compilesWithoutError()?.withWarningContaining(
+                ProcessorErrors.cursorPojoMismatch(
+                        pojoTypeName = POJO,
+                        unusedColumns = listOf("uid"),
+                        unusedFields = listOf(createField("lastName")),
+                        allColumns = listOf("uid", "name"),
+                        allFields = listOf(createField("name"), createField("lastName"))
+                ))
+    }
+
+    fun pojoTest(pojoFields: String, queryColumns: List<String>,
+                 handler: (PojoRowAdapter?, QueryMethod, TestInvocation) -> Unit): CompileTester? {
+        val assertion = singleQueryMethod(
+                """
+                static class Pojo {
+                    $pojoFields
+                }
+                @Query("SELECT ${queryColumns.joinToString(", ")} from User LIMIT 1")
+                abstract MyClass.Pojo getNameAndLastNames();
+                """
+        ) { parsedQuery, invocation ->
+            val adapter = parsedQuery.queryResultBinder.adapter
+            if (enableVerification) {
+                if (adapter is SingleEntityQueryResultAdapter) {
+                    handler(adapter.rowAdapter as? PojoRowAdapter, parsedQuery, invocation)
+                } else {
+                    handler(null, parsedQuery, invocation)
+                }
+            } else {
+                assertThat(adapter, nullValue())
+            }
+        }
+        if (enableVerification) {
+            return assertion
+        } else {
+            assertion.failsToCompile().withErrorContaining(CANNOT_FIND_QUERY_RESULT_ADAPTER)
+            return null
+        }
+    }
+
+    fun singleQueryMethod(vararg input: String,
+                          handler: (QueryMethod, TestInvocation) -> Unit):
+            CompileTester {
+        return assertAbout(JavaSourcesSubjectFactory.javaSources())
+                .that(listOf(JavaFileObjects.forSourceString("foo.bar.MyClass",
+                        DAO_PREFIX + input.joinToString("\n") + DAO_SUFFIX
+                ), COMMON.LIVE_DATA, COMMON.COMPUTABLE_LIVE_DATA, COMMON.USER))
+                .processedWith(TestProcessor.builder()
+                        .forAnnotations(Query::class, Dao::class, ColumnInfo::class,
+                                Entity::class, PrimaryKey::class)
+                        .nextRunHandler { invocation ->
+                            val (owner, methods) = invocation.roundEnv
+                                    .getElementsAnnotatedWith(Dao::class.java)
+                                    .map {
+                                        Pair(it,
+                                                invocation.processingEnv.elementUtils
+                                                        .getAllMembers(MoreElements.asType(it))
+                                                        .filter {
+                                                            it.hasAnnotation(Query::class)
+                                                        }
+                                        )
+                                    }.filter { it.second.isNotEmpty() }.first()
+                            val verifier = if (enableVerification) {
+                                createVerifierFromEntities(invocation)
+                            } else {
+                                null
+                            }
+                            val parser = QueryMethodProcessor(
+                                    baseContext = invocation.context,
+                                    containing = MoreTypes.asDeclared(owner.asType()),
+                                    executableElement = MoreElements.asExecutable(methods.first()),
+                                    dbVerifier = verifier)
+                            val parsedQuery = parser.process()
+                            handler(parsedQuery, invocation)
+                            true
+                        }
+                        .build())
+    }
+}
diff --git a/room/compiler/src/test/kotlin/android/arch/persistence/room/processor/ShortcutMethodProcessorTest.kt b/room/compiler/src/test/kotlin/android/arch/persistence/room/processor/ShortcutMethodProcessorTest.kt
new file mode 100644
index 0000000..a4d670c
--- /dev/null
+++ b/room/compiler/src/test/kotlin/android/arch/persistence/room/processor/ShortcutMethodProcessorTest.kt
@@ -0,0 +1,297 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.arch.persistence.room.processor
+
+import android.arch.persistence.room.Dao
+import android.arch.persistence.room.ext.CommonTypeNames
+import android.arch.persistence.room.ext.typeName
+import android.arch.persistence.room.testing.TestInvocation
+import android.arch.persistence.room.testing.TestProcessor
+import android.arch.persistence.room.vo.ShortcutMethod
+import com.google.auto.common.MoreElements
+import com.google.auto.common.MoreTypes
+import com.google.common.truth.Truth
+import com.google.testing.compile.CompileTester
+import com.google.testing.compile.JavaFileObjects
+import com.google.testing.compile.JavaSourcesSubjectFactory
+import com.squareup.javapoet.ArrayTypeName
+import com.squareup.javapoet.ClassName
+import com.squareup.javapoet.ParameterizedTypeName
+import com.squareup.javapoet.TypeName
+import org.hamcrest.CoreMatchers
+import org.hamcrest.CoreMatchers.`is`
+import org.hamcrest.MatcherAssert.assertThat
+import org.junit.Test
+import javax.lang.model.element.ExecutableElement
+import javax.lang.model.type.DeclaredType
+import kotlin.reflect.KClass
+
+/**
+ * Base test class for shortcut methods.
+ */
+abstract class ShortcutMethodProcessorTest<out T : ShortcutMethod>(
+        val annotation: KClass<out Annotation>) {
+    companion object {
+        const val DAO_PREFIX = """
+                package foo.bar;
+                import android.arch.persistence.room.*;
+                import java.util.*;
+                @Dao
+                abstract class MyClass {
+                """
+        const val DAO_SUFFIX = "}"
+        val USER_TYPE_NAME: TypeName = COMMON.USER_TYPE_NAME
+        val BOOK_TYPE_NAME : TypeName = ClassName.get("foo.bar", "Book")
+    }
+
+    @Test
+    fun noParams() {
+        singleShortcutMethod(
+                """
+                @${annotation.java.canonicalName}
+                abstract public void foo();
+                """) { shortcut, invocation ->
+            assertThat(shortcut.name, `is`("foo"))
+            assertThat(shortcut.parameters.size, `is`(0))
+            assertThat(shortcut.returnCount, `is`(false))
+        }.failsToCompile().withErrorContaining(noParamsError())
+    }
+
+    abstract fun noParamsError(): String
+
+    @Test
+    fun single() {
+        singleShortcutMethod(
+                """
+                @${annotation.java.canonicalName}
+                abstract public int foo(User user);
+                """) { shortcut, invocation ->
+            assertThat(shortcut.name, `is`("foo"))
+            assertThat(shortcut.parameters.size, `is`(1))
+            val param = shortcut.parameters.first()
+            assertThat(param.type.typeName(), `is`(USER_TYPE_NAME))
+            assertThat(param.entityType?.typeName(), `is`(USER_TYPE_NAME))
+            assertThat(shortcut.entities.size, `is`(1))
+            assertThat(shortcut.entities["user"]?.typeName, `is`(USER_TYPE_NAME))
+            assertThat(shortcut.returnCount, `is`(true))
+        }.compilesWithoutError()
+    }
+
+    @Test
+    fun notAnEntity() {
+        singleShortcutMethod(
+                """
+                @${annotation.java.canonicalName}
+                abstract public void foo(NotAnEntity notValid);
+                """) { shortcut, invocation ->
+            assertThat(shortcut.name, `is`("foo"))
+            assertThat(shortcut.parameters.size, `is`(1))
+            val param = shortcut.parameters.first()
+            assertThat(param.entityType, `is`(CoreMatchers.nullValue()))
+            assertThat(shortcut.entities.size, `is`(0))
+        }.failsToCompile().withErrorContaining(
+                ProcessorErrors.CANNOT_FIND_ENTITY_FOR_SHORTCUT_QUERY_PARAMETER
+        )
+    }
+
+    @Test
+    fun two() {
+        singleShortcutMethod(
+                """
+                @${annotation.java.canonicalName}
+                abstract public void foo(User u1, User u2);
+                """) { shortcut, invocation ->
+            assertThat(shortcut.name, `is`("foo"))
+
+            assertThat(shortcut.parameters.size, `is`(2))
+            shortcut.parameters.forEach {
+                assertThat(it.type.typeName(), `is`(USER_TYPE_NAME))
+                assertThat(it.entityType?.typeName(), `is`(USER_TYPE_NAME))
+            }
+            assertThat(shortcut.entities.size, `is`(2))
+            assertThat(shortcut.entities["u1"]?.typeName, `is`(USER_TYPE_NAME))
+            assertThat(shortcut.entities["u1"]?.typeName, `is`(USER_TYPE_NAME))
+            assertThat(shortcut.parameters.map { it.name },
+                    `is`(listOf("u1", "u2")))
+            assertThat(shortcut.returnCount, `is`(false))
+        }.compilesWithoutError()
+    }
+
+    @Test
+    fun list() {
+        singleShortcutMethod(
+                """
+                @${annotation.java.canonicalName}
+                abstract public int users(List<User> users);
+                """) { shortcut, invocation ->
+            assertThat(shortcut.name, `is`("users"))
+            assertThat(shortcut.parameters.size, `is`(1))
+            val param = shortcut.parameters.first()
+            assertThat(param.type.typeName(), `is`(
+                    ParameterizedTypeName.get(
+                            ClassName.get("java.util", "List"), USER_TYPE_NAME) as TypeName))
+            assertThat(param.entityType?.typeName(), `is`(USER_TYPE_NAME))
+            assertThat(shortcut.entities.size, `is`(1))
+            assertThat(shortcut.entities["users"]?.typeName, `is`(USER_TYPE_NAME))
+            assertThat(shortcut.returnCount, `is`(true))
+        }.compilesWithoutError()
+    }
+
+    @Test
+    fun array() {
+        singleShortcutMethod(
+                """
+                @${annotation.java.canonicalName}
+                abstract public void users(User[] users);
+                """) { shortcut, invocation ->
+            assertThat(shortcut.name, `is`("users"))
+            assertThat(shortcut.parameters.size, `is`(1))
+            val param = shortcut.parameters.first()
+            assertThat(param.type.typeName(), `is`(
+                    ArrayTypeName.of(COMMON.USER_TYPE_NAME) as TypeName))
+            assertThat(shortcut.entities.size, `is`(1))
+            assertThat(shortcut.entities["users"]?.typeName, `is`(USER_TYPE_NAME))
+            assertThat(shortcut.returnCount, `is`(false))
+        }.compilesWithoutError()
+    }
+
+    @Test
+    fun set() {
+        singleShortcutMethod(
+                """
+                @${annotation.java.canonicalName}
+                abstract public void modifyUsers(Set<User> users);
+                """) { shortcut, invocation ->
+            assertThat(shortcut.name, `is`("modifyUsers"))
+            assertThat(shortcut.parameters.size, `is`(1))
+            val param = shortcut.parameters.first()
+            assertThat(param.type.typeName(), `is`(
+                    ParameterizedTypeName.get(ClassName.get("java.util", "Set")
+                            , COMMON.USER_TYPE_NAME) as TypeName))
+            assertThat(shortcut.entities.size, `is`(1))
+            assertThat(shortcut.entities["users"]?.typeName, `is`(USER_TYPE_NAME))
+            assertThat(shortcut.returnCount, `is`(false))
+        }.compilesWithoutError()
+    }
+
+    @Test
+    fun iterable() {
+        singleShortcutMethod(
+                """
+                @${annotation.java.canonicalName}
+                abstract public void modifyUsers(Iterable<User> users);
+                """) { shortcut, invocation ->
+            assertThat(shortcut.name, `is`("modifyUsers"))
+            assertThat(shortcut.parameters.size, `is`(1))
+            val param = shortcut.parameters.first()
+            assertThat(param.type.typeName(), `is`(
+                    ParameterizedTypeName.get(ClassName.get("java.lang", "Iterable")
+                            , COMMON.USER_TYPE_NAME) as TypeName))
+            assertThat(shortcut.entities.size, `is`(1))
+            assertThat(shortcut.entities["users"]?.typeName, `is`(USER_TYPE_NAME))
+            assertThat(shortcut.returnCount, `is`(false))
+        }.compilesWithoutError()
+    }
+
+    @Test
+    fun customCollection() {
+        singleShortcutMethod(
+                """
+                static class MyList<Irrelevant, Item> extends ArrayList<Item> {}
+                @${annotation.java.canonicalName}
+                abstract public void modifyUsers(MyList<String, User> users);
+                """) { shortcut, invocation ->
+            assertThat(shortcut.name, `is`("modifyUsers"))
+            assertThat(shortcut.parameters.size, `is`(1))
+            val param = shortcut.parameters.first()
+            assertThat(param.type.typeName(), `is`(
+                    ParameterizedTypeName.get(ClassName.get("foo.bar", "MyClass.MyList")
+                            , CommonTypeNames.STRING
+                            , COMMON.USER_TYPE_NAME) as TypeName))
+            assertThat(shortcut.entities.size, `is`(1))
+            assertThat(shortcut.entities["users"]?.typeName, `is`(USER_TYPE_NAME))
+            assertThat(shortcut.returnCount, `is`(false))
+        }.compilesWithoutError()
+    }
+
+    @Test
+    fun differentTypes() {
+        singleShortcutMethod(
+                """
+                @${annotation.java.canonicalName}
+                abstract public void foo(User u1, Book b1);
+                """) { shortcut, invocation ->
+            assertThat(shortcut.parameters.size, `is`(2))
+            assertThat(shortcut.parameters[0].type.typeName().toString(),
+                    `is`("foo.bar.User"))
+            assertThat(shortcut.parameters[1].type.typeName().toString(),
+                    `is`("foo.bar.Book"))
+            assertThat(shortcut.parameters.map { it.name }, `is`(listOf("u1", "b1")))
+            assertThat(shortcut.returnCount, `is`(false))
+            assertThat(shortcut.entities.size, `is`(2))
+            assertThat(shortcut.entities["u1"]?.typeName, `is`(USER_TYPE_NAME))
+            assertThat(shortcut.entities["b1"]?.typeName, `is`(BOOK_TYPE_NAME))
+        }.compilesWithoutError()
+    }
+
+    @Test
+    fun invalidReturnType() {
+        singleShortcutMethod(
+                """
+                @${annotation.java.canonicalName}
+                abstract public long foo(User user);
+                """) { shortcut, invocation ->
+        }.failsToCompile().withErrorContaining(invalidReturnTypeError())
+    }
+
+    abstract fun invalidReturnTypeError(): String
+
+    abstract fun process(baseContext: Context, containing: DeclaredType,
+                         executableElement: ExecutableElement): T
+
+    fun singleShortcutMethod(vararg input: String,
+                             handler: (T, TestInvocation) -> Unit):
+            CompileTester {
+        return Truth.assertAbout(JavaSourcesSubjectFactory.javaSources())
+                .that(listOf(JavaFileObjects.forSourceString("foo.bar.MyClass",
+                        DAO_PREFIX + input.joinToString("\n") + DAO_SUFFIX
+                ), COMMON.USER, COMMON.BOOK, COMMON.NOT_AN_ENTITY))
+                .processedWith(TestProcessor.builder()
+                        .forAnnotations(annotation, Dao::class)
+                        .nextRunHandler { invocation ->
+                            val (owner, methods) = invocation.roundEnv
+                                    .getElementsAnnotatedWith(Dao::class.java)
+                                    .map {
+                                        Pair(it,
+                                                invocation.processingEnv.elementUtils
+                                                        .getAllMembers(MoreElements.asType(it))
+                                                        .filter {
+                                                            MoreElements.isAnnotationPresent(it,
+                                                                    annotation.java)
+                                                        }
+                                        )
+                                    }.filter { it.second.isNotEmpty() }.first()
+                            val processed = process(
+                                    baseContext = invocation.context,
+                                    containing = MoreTypes.asDeclared(owner.asType()),
+                                    executableElement = MoreElements.asExecutable(methods.first()))
+                            handler(processed, invocation)
+                            true
+                        }
+                        .build())
+    }
+}
diff --git a/room/compiler/src/test/kotlin/android/arch/persistence/room/processor/UpdateMethodProcessorTest.kt b/room/compiler/src/test/kotlin/android/arch/persistence/room/processor/UpdateMethodProcessorTest.kt
new file mode 100644
index 0000000..c8a88ae
--- /dev/null
+++ b/room/compiler/src/test/kotlin/android/arch/persistence/room/processor/UpdateMethodProcessorTest.kt
@@ -0,0 +1,64 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package android.arch.persistence.room.processor
+
+import android.arch.persistence.room.OnConflictStrategy
+import android.arch.persistence.room.Update
+import android.arch.persistence.room.processor.ProcessorErrors
+        .UPDATE_METHODS_MUST_RETURN_VOID_OR_INT
+import android.arch.persistence.room.processor.ProcessorErrors.UPDATE_MISSING_PARAMS
+import android.arch.persistence.room.vo.UpdateMethod
+import org.hamcrest.CoreMatchers.`is`
+import org.hamcrest.MatcherAssert.assertThat
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.junit.runners.JUnit4
+import javax.lang.model.element.ExecutableElement
+import javax.lang.model.type.DeclaredType
+
+@Suppress("PLATFORM_CLASS_MAPPED_TO_KOTLIN")
+@RunWith(JUnit4::class)
+class UpdateMethodProcessorTest : ShortcutMethodProcessorTest<UpdateMethod>(Update::class) {
+    override fun invalidReturnTypeError(): String = UPDATE_METHODS_MUST_RETURN_VOID_OR_INT
+
+    override fun noParamsError(): String = UPDATE_MISSING_PARAMS
+
+    override fun process(baseContext: Context, containing: DeclaredType,
+                         executableElement: ExecutableElement): UpdateMethod {
+        return UpdateMethodProcessor(baseContext, containing, executableElement).process()
+    }
+
+    @Test
+    fun goodConflict() {
+        singleShortcutMethod(
+                """
+                @Update(onConflict = OnConflictStrategy.REPLACE)
+                abstract public void foo(User user);
+                """) { shortcut, invocation ->
+            assertThat(shortcut.onConflictStrategy, `is`(OnConflictStrategy.REPLACE))
+        }.compilesWithoutError()
+    }
+
+    @Test
+    fun badConflict() {
+        singleShortcutMethod(
+                """
+                @Update(onConflict = -1)
+                abstract public void foo(User user);
+                """) { shortcut, invocation ->
+        }.failsToCompile().withErrorContaining(ProcessorErrors.INVALID_ON_CONFLICT_VALUE)
+    }
+}
diff --git a/room/compiler/src/test/kotlin/android/arch/persistence/room/solver/BasicColumnTypeAdaptersTest.kt b/room/compiler/src/test/kotlin/android/arch/persistence/room/solver/BasicColumnTypeAdaptersTest.kt
new file mode 100644
index 0000000..024099b
--- /dev/null
+++ b/room/compiler/src/test/kotlin/android/arch/persistence/room/solver/BasicColumnTypeAdaptersTest.kt
@@ -0,0 +1,190 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.arch.persistence.room.solver
+
+import android.arch.persistence.room.processor.Context
+import android.arch.persistence.room.testing.TestInvocation
+import com.squareup.javapoet.ClassName
+import com.squareup.javapoet.FieldSpec
+import com.squareup.javapoet.JavaFile
+import com.squareup.javapoet.MethodSpec
+import com.squareup.javapoet.TypeName
+import com.squareup.javapoet.TypeSpec
+import org.hamcrest.CoreMatchers.`is`
+import org.hamcrest.MatcherAssert.assertThat
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.junit.runners.Parameterized
+import simpleRun
+import testCodeGenScope
+import javax.annotation.processing.ProcessingEnvironment
+import javax.lang.model.type.PrimitiveType
+import javax.lang.model.type.TypeKind
+import javax.lang.model.type.TypeMirror
+
+@RunWith(Parameterized::class)
+class BasicColumnTypeAdaptersTest(val input: Input, val bindCode: String,
+                                  val cursorCode: String) {
+    val scope = testCodeGenScope()
+
+    companion object {
+        val SQLITE_STMT: TypeName = ClassName.get("android.database.sqlite", "SQLiteStatement")
+        val CURSOR: TypeName = ClassName.get("android.database", "Cursor")
+
+        @Parameterized.Parameters(name = "kind:{0},bind:_{1},cursor:_{2}")
+        @JvmStatic
+        fun params(): List<Array<Any>> {
+            return listOf(
+                    arrayOf(Input(TypeKind.INT),
+                            "st.bindLong(6, inp);",
+                            "out = crs.getInt(9);"),
+                    arrayOf(Input(TypeKind.BYTE),
+                            "st.bindLong(6, inp);",
+                            "out = (byte) crs.getShort(9);"),
+                    arrayOf(Input(TypeKind.SHORT),
+                            "st.bindLong(6, inp);",
+                            "out = crs.getShort(9);"),
+                    arrayOf(Input(TypeKind.LONG),
+                            "st.bindLong(6, inp);",
+                            "out = crs.getLong(9);"),
+                    arrayOf(Input(TypeKind.CHAR),
+                            "st.bindLong(6, inp);",
+                            "out = (char) crs.getInt(9);"),
+                    arrayOf(Input(TypeKind.FLOAT),
+                            "st.bindDouble(6, inp);",
+                            "out = crs.getFloat(9);"),
+                    arrayOf(Input(TypeKind.DOUBLE),
+                            "st.bindDouble(6, inp);",
+                            "out = crs.getDouble(9);"),
+                    arrayOf(Input(TypeKind.DECLARED, "java.lang.String"),
+                            """
+                            if (inp == null) {
+                              st.bindNull(6);
+                            } else {
+                              st.bindString(6, inp);
+                            }
+                            """.trimIndent(),
+                            "out = crs.getString(9);")
+            )
+        }
+    }
+
+    @Test
+    fun bind() {
+        simpleRun { invocation ->
+            val adapter = TypeAdapterStore.create(Context(invocation.processingEnv))
+                    .findColumnTypeAdapter(input.getTypeMirror(invocation.processingEnv), null)!!
+            adapter.bindToStmt("st", "6", "inp", scope)
+            assertThat(scope.generate().trim(), `is`(bindCode))
+            generateCode(invocation, false)
+        }.compilesWithoutError()
+    }
+
+    @Test
+    fun boxedBind() {
+        if (!input.typeKind.isPrimitive) {
+            return // no-op for those
+        }
+        simpleRun { invocation ->
+            val adapter = TypeAdapterStore.create(Context(invocation.processingEnv))
+                    .findColumnTypeAdapter(
+                            input.getBoxedTypeMirror(invocation.processingEnv), null)!!
+            adapter.bindToStmt("st", "6", "inp", scope)
+            assertThat(scope.generate().trim(), `is`(
+                    """
+                    if (inp == null) {
+                      st.bindNull(6);
+                    } else {
+                      $bindCode
+                    }
+                    """.trimIndent()
+            ))
+            generateCode(invocation, true)
+        }.compilesWithoutError()
+    }
+
+    private fun generateCode(invocation: TestInvocation, boxed: Boolean) {
+        val typeMirror = if (boxed) input.getBoxedTypeMirror(invocation.processingEnv)
+        else input.getTypeMirror(invocation.processingEnv)
+        val spec = TypeSpec.classBuilder("OutClass")
+                .addField(FieldSpec.builder(SQLITE_STMT, "st").build())
+                .addField(FieldSpec.builder(CURSOR, "crs").build())
+                .addField(FieldSpec.builder(TypeName.get(typeMirror), "out").build())
+                .addField(FieldSpec.builder(TypeName.get(typeMirror), "inp").build())
+                .addMethod(
+                        MethodSpec.methodBuilder("foo")
+                                .addCode(scope.builder().build())
+                                .build()
+                )
+                .build()
+        JavaFile.builder("foo.bar", spec).build().writeTo(invocation.processingEnv.filer)
+    }
+
+    @Test
+    fun read() {
+        simpleRun { invocation ->
+            val adapter = TypeAdapterStore.create(Context(invocation.processingEnv))
+                    .findColumnTypeAdapter(input.getTypeMirror(invocation.processingEnv), null)!!
+            adapter.readFromCursor("out", "crs", "9", scope)
+            assertThat(scope.generate().trim(), `is`(cursorCode))
+            generateCode(invocation, false)
+        }.compilesWithoutError()
+    }
+
+    @Test
+    fun readBoxed() {
+        if (!input.typeKind.isPrimitive) {
+            return // no-op for those
+        }
+        simpleRun { invocation ->
+            val adapter = TypeAdapterStore.create(Context(invocation.processingEnv))
+                    .findColumnTypeAdapter(
+                            input.getBoxedTypeMirror(invocation.processingEnv), null)!!
+            adapter.readFromCursor("out", "crs", "9", scope)
+            assertThat(scope.generate().trim(), `is`(
+                    """
+                    if (crs.isNull(9)) {
+                      out = null;
+                    } else {
+                      $cursorCode
+                    }
+                    """.trimIndent()
+            ))
+            generateCode(invocation, true)
+        }.compilesWithoutError()
+    }
+
+    data class Input(val typeKind: TypeKind, val qName: String? = null) {
+        fun getTypeMirror(processingEnv: ProcessingEnvironment): TypeMirror {
+            return if (typeKind.isPrimitive) {
+                processingEnv.typeUtils.getPrimitiveType(typeKind)
+            } else {
+                processingEnv.elementUtils.getTypeElement(qName).asType()
+            }
+        }
+
+        fun getBoxedTypeMirror(processingEnv: ProcessingEnvironment): TypeMirror {
+            return if (typeKind.isPrimitive) {
+                processingEnv.typeUtils
+                        .boxedClass(getTypeMirror(processingEnv) as PrimitiveType)
+                        .asType()
+            } else {
+                getTypeMirror(processingEnv)
+            }
+        }
+    }
+}
diff --git a/room/compiler/src/test/kotlin/android/arch/persistence/room/solver/CustomTypeConverterResolutionTest.kt b/room/compiler/src/test/kotlin/android/arch/persistence/room/solver/CustomTypeConverterResolutionTest.kt
new file mode 100644
index 0000000..1790788
--- /dev/null
+++ b/room/compiler/src/test/kotlin/android/arch/persistence/room/solver/CustomTypeConverterResolutionTest.kt
@@ -0,0 +1,300 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+@file:Suppress("HasPlatformType")
+
+package android.arch.persistence.room.solver
+
+import android.arch.persistence.room.Dao
+import android.arch.persistence.room.Database
+import android.arch.persistence.room.Entity
+import android.arch.persistence.room.PrimaryKey
+import android.arch.persistence.room.Query
+import android.arch.persistence.room.RoomProcessor
+import android.arch.persistence.room.TypeConverter
+import android.arch.persistence.room.TypeConverters
+import android.arch.persistence.room.ext.RoomTypeNames
+import android.arch.persistence.room.ext.S
+import android.arch.persistence.room.ext.T
+import android.arch.persistence.room.processor.ProcessorErrors.CANNOT_BIND_QUERY_PARAMETER_INTO_STMT
+import android.arch.persistence.room.testing.TestInvocation
+import com.google.common.truth.Truth
+import com.google.testing.compile.CompileTester
+import com.google.testing.compile.JavaFileObjects
+import com.google.testing.compile.JavaSourcesSubjectFactory
+import com.squareup.javapoet.AnnotationSpec
+import com.squareup.javapoet.ClassName
+import com.squareup.javapoet.FieldSpec
+import com.squareup.javapoet.MethodSpec
+import com.squareup.javapoet.ParameterSpec
+import com.squareup.javapoet.TypeName
+import com.squareup.javapoet.TypeSpec
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.junit.runners.JUnit4
+import javax.lang.model.element.Modifier
+import javax.tools.JavaFileObject
+
+@RunWith(JUnit4::class)
+class CustomTypeConverterResolutionTest {
+    fun TypeSpec.toJFO() : JavaFileObject {
+        return JavaFileObjects.forSourceString("foo.bar.${this.name}",
+                "package foo.bar;\n" + toString())
+    }
+
+    companion object {
+        val ENTITY = ClassName.get("foo.bar", "MyEntity")
+        val DB = ClassName.get("foo.bar", "MyDb")
+        val DAO = ClassName.get("foo.bar", "MyDao")
+
+        val CUSTOM_TYPE = ClassName.get("foo.bar", "CustomType")
+        val CUSTOM_TYPE_JFO = JavaFileObjects.forSourceLines(CUSTOM_TYPE.toString(),
+                """
+                package ${CUSTOM_TYPE.packageName()};
+                public class ${CUSTOM_TYPE.simpleName()} {
+                    public int value;
+                }
+                """)
+        val CUSTOM_TYPE_CONVERTER = ClassName.get("foo.bar", "MyConverter")
+        val CUSTOM_TYPE_CONVERTER_JFO = JavaFileObjects.forSourceLines(
+                CUSTOM_TYPE_CONVERTER.toString(),
+                """
+                package ${CUSTOM_TYPE_CONVERTER.packageName()};
+                public class ${CUSTOM_TYPE_CONVERTER.simpleName()} {
+                    @${TypeConverter::class.java.canonicalName}
+                    public static $CUSTOM_TYPE toCustom(int value) {
+                        return null;
+                    }
+                    @${TypeConverter::class.java.canonicalName}
+                    public static int fromCustom($CUSTOM_TYPE input) {
+                        return 0;
+                    }
+                }
+                """)
+    }
+
+    @Test
+    fun useFromDatabase_forEntity() {
+        val entity = createEntity(hasCustomField = true)
+        val database = createDatabase(hasConverters = true, hasDao = true)
+        val dao = createDao(hasQueryReturningEntity = true, hasQueryWithCustomParam = true)
+        run(entity.toJFO(), dao.toJFO(), database.toJFO()){
+
+        }.compilesWithoutError()
+    }
+
+    @Test
+    fun useFromDatabase_forQueryParameter() {
+        val entity = createEntity()
+        val database = createDatabase(hasConverters = true, hasDao = true)
+        val dao = createDao(hasQueryWithCustomParam = true)
+        run(entity.toJFO(), dao.toJFO(), database.toJFO()){
+
+        }.compilesWithoutError()
+    }
+
+    @Test
+    fun useFromDatabase_forReturnValue() {
+        val entity = createEntity(hasCustomField = true)
+        val database = createDatabase(hasConverters = true, hasDao = true)
+        val dao = createDao(hasQueryReturningEntity = true)
+        run(entity.toJFO(), dao.toJFO(), database.toJFO()){
+
+        }.compilesWithoutError()
+    }
+
+    @Test
+    fun useFromDao_forQueryParameter() {
+        val entity = createEntity()
+        val database = createDatabase(hasDao = true)
+        val dao = createDao(hasConverters = true, hasQueryReturningEntity = true,
+                hasQueryWithCustomParam = true)
+        run(entity.toJFO(), dao.toJFO(), database.toJFO()){
+
+        }.compilesWithoutError()
+    }
+
+    @Test
+    fun useFromEntity_forReturnValue() {
+        val entity = createEntity(hasCustomField = true, hasConverters = true)
+        val database = createDatabase(hasDao = true)
+        val dao = createDao(hasQueryReturningEntity = true)
+        run(entity.toJFO(), dao.toJFO(), database.toJFO()){
+
+        }.compilesWithoutError()
+    }
+
+    @Test
+    fun useFromEntityField_forReturnValue() {
+        val entity = createEntity(hasCustomField = true, hasConverterOnField = true)
+        val database = createDatabase(hasDao = true)
+        val dao = createDao(hasQueryReturningEntity = true)
+        run(entity.toJFO(), dao.toJFO(), database.toJFO()){
+
+        }.compilesWithoutError()
+    }
+
+    @Test
+    fun useFromEntity_forQueryParameter() {
+        val entity = createEntity(hasCustomField = true, hasConverters = true)
+        val database = createDatabase(hasDao = true)
+        val dao = createDao(hasQueryWithCustomParam = true)
+        run(entity.toJFO(), dao.toJFO(), database.toJFO()){
+
+        }.failsToCompile().withErrorContaining(CANNOT_BIND_QUERY_PARAMETER_INTO_STMT)
+    }
+
+    @Test
+    fun useFromEntityField_forQueryParameter() {
+        val entity = createEntity(hasCustomField = true, hasConverterOnField = true)
+        val database = createDatabase(hasDao = true)
+        val dao = createDao(hasQueryWithCustomParam = true)
+        run(entity.toJFO(), dao.toJFO(), database.toJFO()){
+
+        }.failsToCompile().withErrorContaining(CANNOT_BIND_QUERY_PARAMETER_INTO_STMT)
+    }
+
+    @Test
+    fun useFromQueryMethod_forQueryParameter() {
+        val entity = createEntity()
+        val database = createDatabase(hasDao = true)
+        val dao = createDao(hasQueryWithCustomParam = true, hasMethodConverters = true)
+        run(entity.toJFO(), dao.toJFO(), database.toJFO()){
+
+        }.compilesWithoutError()
+    }
+
+    @Test
+    fun useFromQueryParameter_forQueryParameter() {
+        val entity = createEntity()
+        val database = createDatabase(hasDao = true)
+        val dao = createDao(hasQueryWithCustomParam = true, hasParameterConverters = true)
+        run(entity.toJFO(), dao.toJFO(), database.toJFO()){
+
+        }.compilesWithoutError()
+    }
+
+    fun run(vararg jfos : JavaFileObject, f: (TestInvocation) -> Unit): CompileTester {
+        return Truth.assertAbout(JavaSourcesSubjectFactory.javaSources())
+                .that(jfos.toList() + CUSTOM_TYPE_JFO + CUSTOM_TYPE_CONVERTER_JFO)
+                .processedWith(RoomProcessor())
+    }
+
+    private fun createEntity(hasCustomField : Boolean = false,
+                             hasConverters : Boolean = false,
+                             hasConverterOnField : Boolean = false) : TypeSpec {
+        if (hasConverterOnField && hasConverters) {
+            throw IllegalArgumentException("cannot have both converters")
+        }
+        return TypeSpec.classBuilder(ENTITY).apply {
+            addAnnotation(Entity::class.java)
+            addModifiers(Modifier.PUBLIC)
+            if (hasCustomField) {
+                addField(FieldSpec.builder(CUSTOM_TYPE, "myCustomField", Modifier.PUBLIC).apply {
+                    if (hasConverterOnField) {
+                        addAnnotation(createConvertersAnnotation())
+                    }
+                }.build())
+            }
+            if (hasConverters) {
+                addAnnotation(createConvertersAnnotation())
+            }
+            addField(FieldSpec.builder(TypeName.INT, "id", Modifier.PUBLIC).apply {
+                addAnnotation(PrimaryKey::class.java)
+            }.build())
+        }.build()
+    }
+
+    private fun createDatabase(hasConverters : Boolean = false,
+                               hasDao : Boolean = false) : TypeSpec {
+        return TypeSpec.classBuilder(DB).apply {
+            addModifiers(Modifier.ABSTRACT, Modifier.PUBLIC)
+            superclass(RoomTypeNames.ROOM_DB)
+            if (hasConverters) {
+                addAnnotation(createConvertersAnnotation())
+            }
+            addField(FieldSpec.builder(TypeName.INT, "id", Modifier.PUBLIC).apply {
+                addAnnotation(PrimaryKey::class.java)
+            }.build())
+            if (hasDao) {
+                addMethod(MethodSpec.methodBuilder("getDao").apply {
+                    addModifiers(Modifier.PUBLIC, Modifier.ABSTRACT)
+                    returns(DAO)
+                }.build())
+            }
+            addAnnotation(
+                    AnnotationSpec.builder(Database::class.java).apply {
+                        addMember("entities", "{$T.class}", ENTITY)
+                        addMember("version", "42")
+                    }.build()
+            )
+        }.build()
+    }
+
+    private fun createDao(hasConverters : Boolean = false,
+                          hasQueryReturningEntity : Boolean = false,
+                          hasQueryWithCustomParam : Boolean = false,
+                          hasMethodConverters : Boolean = false,
+                          hasParameterConverters : Boolean = false) : TypeSpec {
+        val annotationCount = listOf(hasMethodConverters, hasConverters, hasParameterConverters)
+                .map { if (it) 1 else 0 }.sum()
+        if (annotationCount > 1) {
+            throw IllegalArgumentException("cannot set both of these")
+        }
+        if (hasParameterConverters && !hasQueryWithCustomParam) {
+            throw IllegalArgumentException("inconsistent")
+        }
+        return TypeSpec.classBuilder(DAO).apply {
+            addAnnotation(Dao::class.java)
+            addModifiers(Modifier.ABSTRACT, Modifier.PUBLIC)
+            if (hasConverters) {
+                addAnnotation(createConvertersAnnotation())
+            }
+            if (hasQueryReturningEntity) {
+                addMethod(MethodSpec.methodBuilder("loadAll").apply {
+                    addAnnotation(AnnotationSpec.builder(Query::class.java).apply {
+                        addMember("value", S, "SELECT * FROM ${ENTITY.simpleName()} LIMIT 1")
+                    }.build())
+                    addModifiers(Modifier.ABSTRACT)
+                    returns(ENTITY)
+                }.build())
+            }
+            if (hasQueryWithCustomParam) {
+                addMethod(MethodSpec.methodBuilder("queryWithCustom").apply {
+                    addAnnotation(AnnotationSpec.builder(Query::class.java).apply {
+                        addMember("value", S, "SELECT COUNT(*) FROM ${ENTITY.simpleName()} where" +
+                                " id IN(:customs)")
+                    }.build())
+                    if (hasMethodConverters) {
+                        addAnnotation(createConvertersAnnotation())
+                    }
+                    addParameter(ParameterSpec.builder(CUSTOM_TYPE, "customs").apply {
+                        if (hasParameterConverters) {
+                            addAnnotation(createConvertersAnnotation())
+                        }
+                    }.build())
+                    addModifiers(Modifier.ABSTRACT)
+                    returns(TypeName.INT)
+                }.build())
+            }
+        }.build()
+    }
+
+    private fun createConvertersAnnotation(): AnnotationSpec {
+        return AnnotationSpec.builder(TypeConverters::class.java)
+                .addMember("value", "$T.class", CUSTOM_TYPE_CONVERTER).build()
+    }
+}
diff --git a/room/compiler/src/test/kotlin/android/arch/persistence/room/solver/TypeAdapterStoreTest.kt b/room/compiler/src/test/kotlin/android/arch/persistence/room/solver/TypeAdapterStoreTest.kt
new file mode 100644
index 0000000..524d18d
--- /dev/null
+++ b/room/compiler/src/test/kotlin/android/arch/persistence/room/solver/TypeAdapterStoreTest.kt
@@ -0,0 +1,365 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.arch.persistence.room.solver
+
+import COMMON
+import android.arch.persistence.room.Entity
+import android.arch.persistence.room.ext.L
+import android.arch.persistence.room.ext.LifecyclesTypeNames
+import android.arch.persistence.room.ext.ReactiveStreamsTypeNames
+import android.arch.persistence.room.ext.RoomTypeNames.STRING_UTIL
+import android.arch.persistence.room.ext.RxJava2TypeNames
+import android.arch.persistence.room.ext.T
+import android.arch.persistence.room.parser.SQLTypeAffinity
+import android.arch.persistence.room.processor.Context
+import android.arch.persistence.room.processor.ProcessorErrors
+import android.arch.persistence.room.solver.types.CompositeAdapter
+import android.arch.persistence.room.solver.types.TypeConverter
+import android.arch.persistence.room.testing.TestInvocation
+import android.arch.persistence.room.testing.TestProcessor
+import com.google.auto.common.MoreTypes
+import com.google.common.truth.Truth
+import com.google.testing.compile.CompileTester
+import com.google.testing.compile.JavaFileObjects
+import com.google.testing.compile.JavaSourcesSubjectFactory
+import org.hamcrest.CoreMatchers.`is`
+import org.hamcrest.CoreMatchers.instanceOf
+import org.hamcrest.CoreMatchers.notNullValue
+import org.hamcrest.CoreMatchers.nullValue
+import org.hamcrest.MatcherAssert.assertThat
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.junit.runners.JUnit4
+import simpleRun
+import testCodeGenScope
+import javax.annotation.processing.ProcessingEnvironment
+import javax.lang.model.type.TypeKind
+
+@Suppress("PLATFORM_CLASS_MAPPED_TO_KOTLIN")
+@RunWith(JUnit4::class)
+class TypeAdapterStoreTest {
+    companion object {
+        fun tmp(index: Int) = CodeGenScope._tmpVar(index)
+    }
+
+    @Test
+    fun testDirect() {
+        singleRun { invocation ->
+            val store = TypeAdapterStore.create(Context(invocation.processingEnv))
+            val primitiveType = invocation.processingEnv.typeUtils.getPrimitiveType(TypeKind.INT)
+            val adapter = store.findColumnTypeAdapter(primitiveType, null)
+            assertThat(adapter, notNullValue())
+        }.compilesWithoutError()
+    }
+
+    @Test
+    fun testVia1TypeAdapter() {
+        singleRun { invocation ->
+            val store = TypeAdapterStore.create(Context(invocation.processingEnv))
+            val booleanType = invocation.processingEnv.typeUtils
+                    .getPrimitiveType(TypeKind.BOOLEAN)
+            val adapter = store.findColumnTypeAdapter(booleanType, null)
+            assertThat(adapter, notNullValue())
+            assertThat(adapter, instanceOf(CompositeAdapter::class.java))
+            val bindScope = testCodeGenScope()
+            adapter!!.bindToStmt("stmt", "41", "fooVar", bindScope)
+            assertThat(bindScope.generate().trim(), `is`("""
+                    final int ${tmp(0)};
+                    ${tmp(0)} = fooVar ? 1 : 0;
+                    stmt.bindLong(41, ${tmp(0)});
+                    """.trimIndent()))
+
+            val cursorScope = testCodeGenScope()
+            adapter.readFromCursor("res", "curs", "7", cursorScope)
+            assertThat(cursorScope.generate().trim(), `is`("""
+                    final int ${tmp(0)};
+                    ${tmp(0)} = curs.getInt(7);
+                    res = ${tmp(0)} != 0;
+                    """.trimIndent()))
+        }.compilesWithoutError()
+    }
+
+    @Test
+    fun testVia2TypeAdapters() {
+        singleRun { invocation ->
+            val store = TypeAdapterStore.create(Context(invocation.processingEnv),
+                    pointTypeConverters(invocation.processingEnv))
+            val pointType = invocation.processingEnv.elementUtils
+                    .getTypeElement("foo.bar.Point").asType()
+            val adapter = store.findColumnTypeAdapter(pointType, null)
+            assertThat(adapter, notNullValue())
+            assertThat(adapter, instanceOf(CompositeAdapter::class.java))
+
+            val bindScope = testCodeGenScope()
+            adapter!!.bindToStmt("stmt", "41", "fooVar", bindScope)
+            assertThat(bindScope.generate().trim(), `is`("""
+                    final int ${tmp(0)};
+                    final boolean ${tmp(1)};
+                    ${tmp(1)} = foo.bar.Point.toBoolean(fooVar);
+                    ${tmp(0)} = ${tmp(1)} ? 1 : 0;
+                    stmt.bindLong(41, ${tmp(0)});
+                    """.trimIndent()))
+
+            val cursorScope = testCodeGenScope()
+            adapter.readFromCursor("res", "curs", "11", cursorScope).toString()
+            assertThat(cursorScope.generate().trim(), `is`("""
+                    final int ${tmp(0)};
+                    ${tmp(0)} = curs.getInt(11);
+                    final boolean ${tmp(1)};
+                    ${tmp(1)} = ${tmp(0)} != 0;
+                    res = foo.bar.Point.fromBoolean(${tmp(1)});
+                    """.trimIndent()))
+        }.compilesWithoutError()
+    }
+
+    @Test
+    fun testDate() {
+        singleRun { (processingEnv) ->
+            val store = TypeAdapterStore.create(Context(processingEnv),
+                    dateTypeConverters(processingEnv))
+            val tDate = processingEnv.elementUtils.getTypeElement("java.util.Date").asType()
+            val adapter = store.findCursorValueReader(tDate, SQLTypeAffinity.INTEGER)
+            assertThat(adapter, notNullValue())
+            assertThat(adapter?.typeMirror(), `is`(tDate))
+            val bindScope = testCodeGenScope()
+            adapter!!.readFromCursor("outDate", "curs", "0", bindScope)
+            assertThat(bindScope.generate().trim(), `is`("""
+                final java.lang.Long _tmp;
+                if (curs.isNull(0)) {
+                  _tmp = null;
+                } else {
+                  _tmp = curs.getLong(0);
+                }
+                // convert Long to Date;
+            """.trimIndent()))
+        }.compilesWithoutError()
+    }
+
+    @Test
+    fun testIntList() {
+        singleRun { invocation ->
+            val binders = createIntListToStringBinders(invocation)
+            val store = TypeAdapterStore.create(Context(invocation.processingEnv), binders[0],
+                    binders[1])
+
+            val adapter = store.findColumnTypeAdapter(binders[0].from, null)
+            assertThat(adapter, notNullValue())
+
+            val bindScope = testCodeGenScope()
+            adapter!!.bindToStmt("stmt", "41", "fooVar", bindScope)
+            assertThat(bindScope.generate().trim(), `is`("""
+                final java.lang.String ${tmp(0)};
+                ${tmp(0)} = android.arch.persistence.room.util.StringUtil.joinIntoString(fooVar);
+                if (${tmp(0)} == null) {
+                  stmt.bindNull(41);
+                } else {
+                  stmt.bindString(41, ${tmp(0)});
+                }
+                """.trimIndent()))
+
+            val converter = store.findTypeConverter(binders[0].from,
+                    invocation.context.COMMON_TYPES.STRING)
+            assertThat(converter, notNullValue())
+            assertThat(store.reverse(converter!!), `is`(binders[1]))
+
+        }.compilesWithoutError()
+    }
+
+    @Test
+    fun testOneWayConversion() {
+        singleRun { invocation ->
+            val binders = createIntListToStringBinders(invocation)
+            val store = TypeAdapterStore.create(Context(invocation.processingEnv), binders[0])
+            val adapter = store.findColumnTypeAdapter(binders[0].from, null)
+            assertThat(adapter, nullValue())
+
+            val stmtBinder = store.findStatementValueBinder(binders[0].from, null)
+            assertThat(stmtBinder, notNullValue())
+
+            val converter = store.findTypeConverter(binders[0].from,
+                    invocation.context.COMMON_TYPES.STRING)
+            assertThat(converter, notNullValue())
+            assertThat(store.reverse(converter!!), nullValue())
+        }
+    }
+
+    @Test
+    fun testMissingRxRoom() {
+        simpleRun(jfos = *arrayOf(COMMON.PUBLISHER, COMMON.FLOWABLE)) { invocation ->
+            val publisherElement = invocation.processingEnv.elementUtils
+                    .getTypeElement(ReactiveStreamsTypeNames.PUBLISHER.toString())
+            assertThat(publisherElement, notNullValue())
+            assertThat(invocation.context.typeAdapterStore.isRxJava2Publisher(
+                    MoreTypes.asDeclared(publisherElement.asType())), `is`(true))
+        }.failsToCompile().withErrorContaining(ProcessorErrors.MISSING_ROOM_RXJAVA2_ARTIFACT)
+    }
+
+    @Test
+    fun testFindPublisher() {
+        simpleRun(jfos = *arrayOf(COMMON.PUBLISHER, COMMON.FLOWABLE, COMMON.RX2_ROOM)) {
+            invocation ->
+            val publisher = invocation.processingEnv.elementUtils
+                    .getTypeElement(ReactiveStreamsTypeNames.PUBLISHER.toString())
+            assertThat(publisher, notNullValue())
+            assertThat(invocation.context.typeAdapterStore.isRxJava2Publisher(
+                    MoreTypes.asDeclared(publisher.asType())), `is`(true))
+        }.compilesWithoutError()
+    }
+
+    @Test
+    fun testFindFlowable() {
+        simpleRun(jfos = *arrayOf(COMMON.PUBLISHER, COMMON.FLOWABLE, COMMON.RX2_ROOM)) {
+            invocation ->
+            val flowable = invocation.processingEnv.elementUtils
+                    .getTypeElement(RxJava2TypeNames.FLOWABLE.toString())
+            assertThat(flowable, notNullValue())
+            assertThat(invocation.context.typeAdapterStore.isRxJava2Publisher(
+                    MoreTypes.asDeclared(flowable.asType())), `is`(true))
+        }.compilesWithoutError()
+    }
+
+    @Test
+    fun testFindLiveData() {
+        simpleRun(jfos = *arrayOf(COMMON.COMPUTABLE_LIVE_DATA, COMMON.LIVE_DATA)) {
+            invocation ->
+            val liveData = invocation.processingEnv.elementUtils
+                    .getTypeElement(LifecyclesTypeNames.LIVE_DATA.toString())
+            assertThat(liveData, notNullValue())
+            assertThat(invocation.context.typeAdapterStore.isLiveData(
+                    MoreTypes.asDeclared(liveData.asType())), `is`(true))
+        }.compilesWithoutError()
+    }
+
+    private fun createIntListToStringBinders(invocation: TestInvocation): List<TypeConverter> {
+        val intType = invocation.processingEnv.elementUtils
+                .getTypeElement(Integer::class.java.canonicalName)
+                .asType()
+        val listType = invocation.processingEnv.elementUtils
+                .getTypeElement(java.util.List::class.java.canonicalName)
+        val listOfInts = invocation.processingEnv.typeUtils.getDeclaredType(listType, intType)
+
+        val intListConverter = object : TypeConverter(listOfInts,
+                invocation.context.COMMON_TYPES.STRING) {
+            override fun convert(inputVarName: String, outputVarName: String,
+                                 scope: CodeGenScope) {
+                scope.builder().apply {
+                    addStatement("$L = $T.joinIntoString($L)", outputVarName, STRING_UTIL,
+                            inputVarName)
+                }
+            }
+        }
+
+        val stringToIntListConverter = object : TypeConverter(
+                invocation.context.COMMON_TYPES.STRING, listOfInts) {
+            override fun convert(inputVarName: String, outputVarName: String,
+                                 scope: CodeGenScope) {
+                scope.builder().apply {
+                    addStatement("$L = $T.splitToIntList($L)", outputVarName, STRING_UTIL,
+                            inputVarName)
+                }
+            }
+        }
+        return listOf(intListConverter, stringToIntListConverter)
+    }
+
+    fun singleRun(handler: (TestInvocation) -> Unit): CompileTester {
+        return Truth.assertAbout(JavaSourcesSubjectFactory.javaSources())
+                .that(listOf(JavaFileObjects.forSourceString("foo.bar.DummyClass",
+                        """
+                        package foo.bar;
+                        import android.arch.persistence.room.*;
+                        @Entity
+                        public class DummyClass {}
+                        """
+                ), JavaFileObjects.forSourceString("foo.bar.Point",
+                        """
+                        package foo.bar;
+                        import android.arch.persistence.room.*;
+                        @Entity
+                        public class Point {
+                            public int x, y;
+                            public Point(int x, int y) {
+                                this.x = x;
+                                this.y = y;
+                            }
+                            public static Point fromBoolean(boolean val) {
+                                return val ? new Point(1, 1) : new Point(0, 0);
+                            }
+                            public static boolean toBoolean(Point point) {
+                                return point.x > 0;
+                            }
+                        }
+                        """
+                )))
+                .processedWith(TestProcessor.builder()
+                        .forAnnotations(Entity::class)
+                        .nextRunHandler { invocation ->
+                            handler(invocation)
+                            true
+                        }
+                        .build())
+    }
+
+    fun pointTypeConverters(env: ProcessingEnvironment): List<TypeConverter> {
+        val tPoint = env.elementUtils.getTypeElement("foo.bar.Point").asType()
+        val tBoolean = env.typeUtils.getPrimitiveType(TypeKind.BOOLEAN)
+        return listOf(
+                object : TypeConverter(tPoint, tBoolean) {
+                    override fun convert(inputVarName: String, outputVarName: String,
+                                         scope: CodeGenScope) {
+                        scope.builder().apply {
+                            addStatement("$L = $T.toBoolean($L)", outputVarName, from, inputVarName)
+                        }
+                    }
+
+                },
+                object : TypeConverter(tBoolean, tPoint) {
+                    override fun convert(inputVarName: String, outputVarName: String,
+                                         scope: CodeGenScope) {
+                        scope.builder().apply {
+                            addStatement("$L = $T.fromBoolean($L)", outputVarName, tPoint,
+                                    inputVarName)
+                        }
+                    }
+                }
+        )
+    }
+
+    fun dateTypeConverters(env: ProcessingEnvironment): List<TypeConverter> {
+        val tDate = env.elementUtils.getTypeElement("java.util.Date").asType()
+        val tLong = env.elementUtils.getTypeElement("java.lang.Long").asType()
+        return listOf(
+                object : TypeConverter(tDate, tLong) {
+                    override fun convert(inputVarName: String, outputVarName: String,
+                                         scope: CodeGenScope) {
+                        scope.builder().apply {
+                            addStatement("// convert Date to Long")
+                        }
+                    }
+
+                },
+                object : TypeConverter(tLong, tDate) {
+                    override fun convert(inputVarName: String, outputVarName: String,
+                                         scope: CodeGenScope) {
+                        scope.builder().apply {
+                            addStatement("// convert Long to Date")
+                        }
+                    }
+                }
+        )
+    }
+}
diff --git a/room/compiler/src/test/kotlin/android/arch/persistence/room/solver/query/QueryWriterTest.kt b/room/compiler/src/test/kotlin/android/arch/persistence/room/solver/query/QueryWriterTest.kt
new file mode 100644
index 0000000..6401d96
--- /dev/null
+++ b/room/compiler/src/test/kotlin/android/arch/persistence/room/solver/query/QueryWriterTest.kt
@@ -0,0 +1,310 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.arch.persistence.room.solver.query
+
+import android.arch.persistence.room.Dao
+import android.arch.persistence.room.Query
+import android.arch.persistence.room.ext.RoomTypeNames.ROOM_SQL_QUERY
+import android.arch.persistence.room.ext.RoomTypeNames.STRING_UTIL
+import android.arch.persistence.room.processor.QueryMethodProcessor
+import android.arch.persistence.room.testing.TestProcessor
+import android.arch.persistence.room.writer.QueryWriter
+import com.google.auto.common.MoreElements
+import com.google.auto.common.MoreTypes
+import com.google.common.truth.Truth
+import com.google.testing.compile.CompileTester
+import com.google.testing.compile.JavaFileObjects
+import com.google.testing.compile.JavaSourceSubjectFactory
+import org.hamcrest.CoreMatchers.`is`
+import org.hamcrest.MatcherAssert.assertThat
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.junit.runners.JUnit4
+import testCodeGenScope
+
+@RunWith(JUnit4::class)
+class QueryWriterTest {
+    companion object {
+        const val DAO_PREFIX = """
+                package foo.bar;
+                import android.arch.persistence.room.*;
+                import java.util.*;
+                @Dao
+                abstract class MyClass {
+                """
+        const val DAO_SUFFIX = "}"
+        val QUERY = ROOM_SQL_QUERY.toString()
+    }
+
+    @Test
+    fun simpleNoArgQuery() {
+        singleQueryMethod("""
+                @Query("SELECT id FROM users")
+                abstract java.util.List<Integer> selectAllIds();
+                """) { writer ->
+            val scope = testCodeGenScope()
+            writer.prepareReadAndBind("_sql", "_stmt", scope)
+            assertThat(scope.generate().trim(), `is`("""
+                    final java.lang.String _sql = "SELECT id FROM users";
+                    final $QUERY _stmt = $QUERY.acquire(_sql, 0);
+                    """.trimIndent()))
+        }.compilesWithoutError()
+    }
+
+    @Test
+    fun simpleStringArgs() {
+        singleQueryMethod("""
+                @Query("SELECT id FROM users WHERE name LIKE :name")
+                abstract java.util.List<Integer> selectAllIds(String name);
+                """) { writer ->
+            val scope = testCodeGenScope()
+            writer.prepareReadAndBind("_sql", "_stmt", scope)
+            assertThat(scope.generate().trim(), `is`(
+                    """
+                    final java.lang.String _sql = "SELECT id FROM users WHERE name LIKE ?";
+                    final $QUERY _stmt = $QUERY.acquire(_sql, 1);
+                    int _argIndex = 1;
+                    if (name == null) {
+                      _stmt.bindNull(_argIndex);
+                    } else {
+                      _stmt.bindString(_argIndex, name);
+                    }
+                    """.trimIndent()))
+        }.compilesWithoutError()
+    }
+
+    @Test
+    fun twoIntArgs() {
+        singleQueryMethod("""
+                @Query("SELECT id FROM users WHERE id IN(:id1,:id2)")
+                abstract java.util.List<Integer> selectAllIds(int id1, int id2);
+                """) { writer ->
+            val scope = testCodeGenScope()
+            writer.prepareReadAndBind("_sql", "_stmt", scope)
+            assertThat(scope.generate().trim(), `is`(
+                    """
+                    final java.lang.String _sql = "SELECT id FROM users WHERE id IN(?,?)";
+                    final $QUERY _stmt = $QUERY.acquire(_sql, 2);
+                    int _argIndex = 1;
+                    _stmt.bindLong(_argIndex, id1);
+                    _argIndex = 2;
+                    _stmt.bindLong(_argIndex, id2);
+                    """.trimIndent()))
+        }.compilesWithoutError()
+    }
+
+    @Test
+    fun aLongAndIntVarArg() {
+        singleQueryMethod("""
+                @Query("SELECT id FROM users WHERE id IN(:ids) AND age > :time")
+                abstract java.util.List<Integer> selectAllIds(long time, int... ids);
+                """) { writer ->
+            val scope = testCodeGenScope()
+            writer.prepareReadAndBind("_sql", "_stmt", scope)
+            assertThat(scope.generate().trim(), `is`(
+                    """
+                    java.lang.StringBuilder _stringBuilder = $STRING_UTIL.newStringBuilder();
+                    _stringBuilder.append("SELECT id FROM users WHERE id IN(");
+                    final int _inputSize = ids.length;
+                    $STRING_UTIL.appendPlaceholders(_stringBuilder, _inputSize);
+                    _stringBuilder.append(") AND age > ");
+                    _stringBuilder.append("?");
+                    final java.lang.String _sql = _stringBuilder.toString();
+                    final int _argCount = 1 + _inputSize;
+                    final $QUERY _stmt = $QUERY.acquire(_sql, _argCount);
+                    int _argIndex = 1;
+                    for (int _item : ids) {
+                      _stmt.bindLong(_argIndex, _item);
+                      _argIndex ++;
+                    }
+                    _argIndex = 1 + _inputSize;
+                    _stmt.bindLong(_argIndex, time);
+                    """.trimIndent()))
+        }.compilesWithoutError()
+    }
+
+    val collectionOut = """
+                    java.lang.StringBuilder _stringBuilder = $STRING_UTIL.newStringBuilder();
+                    _stringBuilder.append("SELECT id FROM users WHERE id IN(");
+                    final int _inputSize = ids.size();
+                    $STRING_UTIL.appendPlaceholders(_stringBuilder, _inputSize);
+                    _stringBuilder.append(") AND age > ");
+                    _stringBuilder.append("?");
+                    final java.lang.String _sql = _stringBuilder.toString();
+                    final int _argCount = 1 + _inputSize;
+                    final $QUERY _stmt = $QUERY.acquire(_sql, _argCount);
+                    int _argIndex = 1;
+                    for (java.lang.Integer _item : ids) {
+                      if (_item == null) {
+                        _stmt.bindNull(_argIndex);
+                      } else {
+                        _stmt.bindLong(_argIndex, _item);
+                      }
+                      _argIndex ++;
+                    }
+                    _argIndex = 1 + _inputSize;
+                    _stmt.bindLong(_argIndex, time);
+                    """.trimIndent()
+
+    @Test
+    fun aLongAndIntegerList() {
+        singleQueryMethod("""
+                @Query("SELECT id FROM users WHERE id IN(:ids) AND age > :time")
+                abstract List<Integer> selectAllIds(long time, List<Integer> ids);
+                """) { writer ->
+            val scope = testCodeGenScope()
+            writer.prepareReadAndBind("_sql", "_stmt", scope)
+            assertThat(scope.generate().trim(), `is`(collectionOut))
+        }.compilesWithoutError()
+    }
+
+    @Test
+    fun aLongAndIntegerSet() {
+        singleQueryMethod("""
+                @Query("SELECT id FROM users WHERE id IN(:ids) AND age > :time")
+                abstract List<Integer> selectAllIds(long time, Set<Integer> ids);
+                """) { writer ->
+            val scope = testCodeGenScope()
+            writer.prepareReadAndBind("_sql", "_stmt", scope)
+            assertThat(scope.generate().trim(), `is`(collectionOut))
+        }.compilesWithoutError()
+    }
+
+    @Test
+    fun testMultipleBindParamsWithSameName() {
+        singleQueryMethod("""
+                @Query("SELECT id FROM users WHERE age > :age OR bage > :age")
+                abstract List<Integer> selectAllIds(int age);
+                """) { writer ->
+            val scope = testCodeGenScope()
+            writer.prepareReadAndBind("_sql", "_stmt", scope)
+            assertThat(scope.generate().trim(), `is`("""
+                    final java.lang.String _sql = "SELECT id FROM users WHERE age > ? OR bage > ?";
+                    final $QUERY _stmt = $QUERY.acquire(_sql, 2);
+                    int _argIndex = 1;
+                    _stmt.bindLong(_argIndex, age);
+                    _argIndex = 2;
+                    _stmt.bindLong(_argIndex, age);
+                    """.trimIndent()))
+        }.compilesWithoutError()
+    }
+
+    @Test
+    fun testMultipleBindParamsWithSameNameWithVarArg() {
+        singleQueryMethod("""
+                @Query("SELECT id FROM users WHERE age > :age OR bage > :age OR fage IN(:ages)")
+                abstract List<Integer> selectAllIds(int age, int... ages);
+                """) { writer ->
+            val scope = testCodeGenScope()
+            writer.prepareReadAndBind("_sql", "_stmt", scope)
+            assertThat(scope.generate().trim(), `is`("""
+                    java.lang.StringBuilder _stringBuilder = $STRING_UTIL.newStringBuilder();
+                    _stringBuilder.append("SELECT id FROM users WHERE age > ");
+                    _stringBuilder.append("?");
+                    _stringBuilder.append(" OR bage > ");
+                    _stringBuilder.append("?");
+                    _stringBuilder.append(" OR fage IN(");
+                    final int _inputSize = ages.length;
+                    $STRING_UTIL.appendPlaceholders(_stringBuilder, _inputSize);
+                    _stringBuilder.append(")");
+                    final java.lang.String _sql = _stringBuilder.toString();
+                    final int _argCount = 2 + _inputSize;
+                    final $QUERY _stmt = $QUERY.acquire(_sql, _argCount);
+                    int _argIndex = 1;
+                    _stmt.bindLong(_argIndex, age);
+                    _argIndex = 2;
+                    _stmt.bindLong(_argIndex, age);
+                    _argIndex = 3;
+                    for (int _item : ages) {
+                      _stmt.bindLong(_argIndex, _item);
+                      _argIndex ++;
+                    }
+                    """.trimIndent()))
+        }.compilesWithoutError()
+    }
+
+    @Test
+    fun testMultipleBindParamsWithSameNameWithVarArgInTwoBindings() {
+        singleQueryMethod("""
+                @Query("SELECT id FROM users WHERE age IN (:ages) OR bage > :age OR fage IN(:ages)")
+                abstract List<Integer> selectAllIds(int age, int... ages);
+                """) { writer ->
+            val scope = testCodeGenScope()
+            writer.prepareReadAndBind("_sql", "_stmt", scope)
+            assertThat(scope.generate().trim(), `is`("""
+                    java.lang.StringBuilder _stringBuilder = $STRING_UTIL.newStringBuilder();
+                    _stringBuilder.append("SELECT id FROM users WHERE age IN (");
+                    final int _inputSize = ages.length;
+                    $STRING_UTIL.appendPlaceholders(_stringBuilder, _inputSize);
+                    _stringBuilder.append(") OR bage > ");
+                    _stringBuilder.append("?");
+                    _stringBuilder.append(" OR fage IN(");
+                    final int _inputSize_1 = ages.length;
+                    $STRING_UTIL.appendPlaceholders(_stringBuilder, _inputSize_1);
+                    _stringBuilder.append(")");
+                    final java.lang.String _sql = _stringBuilder.toString();
+                    final int _argCount = 1 + _inputSize + _inputSize_1;
+                    final $QUERY _stmt = $QUERY.acquire(_sql, _argCount);
+                    int _argIndex = 1;
+                    for (int _item : ages) {
+                      _stmt.bindLong(_argIndex, _item);
+                      _argIndex ++;
+                    }
+                    _argIndex = 1 + _inputSize;
+                    _stmt.bindLong(_argIndex, age);
+                    _argIndex = 2 + _inputSize;
+                    for (int _item_1 : ages) {
+                      _stmt.bindLong(_argIndex, _item_1);
+                      _argIndex ++;
+                    }
+                    """.trimIndent()))
+        }.compilesWithoutError()
+    }
+
+    fun singleQueryMethod(vararg input: String,
+                          handler: (QueryWriter) -> Unit):
+            CompileTester {
+        return Truth.assertAbout(JavaSourceSubjectFactory.javaSource())
+                .that(JavaFileObjects.forSourceString("foo.bar.MyClass",
+                        DAO_PREFIX + input.joinToString("\n") + DAO_SUFFIX
+                ))
+                .processedWith(TestProcessor.builder()
+                        .forAnnotations(Query::class, Dao::class)
+                        .nextRunHandler { invocation ->
+                            val (owner, methods) = invocation.roundEnv
+                                    .getElementsAnnotatedWith(Dao::class.java)
+                                    .map {
+                                        Pair(it,
+                                                invocation.processingEnv.elementUtils
+                                                        .getAllMembers(MoreElements.asType(it))
+                                                        .filter {
+                                                            MoreElements.isAnnotationPresent(it,
+                                                                    Query::class.java)
+                                                        }
+                                        )
+                                    }.filter { it.second.isNotEmpty() }.first()
+                            val parser = QueryMethodProcessor(
+                                    baseContext = invocation.context,
+                                    containing = MoreTypes.asDeclared(owner.asType()),
+                                    executableElement = MoreElements.asExecutable(methods.first()))
+                            val parsedQuery = parser.process()
+                            handler(QueryWriter(parsedQuery))
+                            true
+                        }
+                        .build())
+    }
+}
diff --git a/room/compiler/src/test/kotlin/android/arch/persistence/room/testing/InProcessorTest.kt b/room/compiler/src/test/kotlin/android/arch/persistence/room/testing/InProcessorTest.kt
new file mode 100644
index 0000000..31e1209
--- /dev/null
+++ b/room/compiler/src/test/kotlin/android/arch/persistence/room/testing/InProcessorTest.kt
@@ -0,0 +1,55 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.arch.persistence.room.testing
+
+import android.arch.persistence.room.Query
+import com.google.common.truth.Truth
+import com.google.testing.compile.JavaFileObjects
+import com.google.testing.compile.JavaSourceSubjectFactory
+import org.hamcrest.CoreMatchers.`is`
+import org.hamcrest.MatcherAssert.assertThat
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.junit.runners.JUnit4
+import java.util.concurrent.atomic.AtomicBoolean
+
+@RunWith(JUnit4::class)
+class InProcessorTest {
+    @Test
+    fun testInProcessorTestRuns() {
+        val didRun = AtomicBoolean(false)
+        Truth.assertAbout(JavaSourceSubjectFactory.javaSource())
+                .that(JavaFileObjects.forSourceString("foo.bar.MyClass",
+                        """
+                        package foo.bar;
+                        abstract public class MyClass {
+                        @android.arch.persistence.room.Query("foo")
+                        abstract public void setFoo(String foo);
+                        }
+                        """))
+                .processedWith(TestProcessor.builder()
+                        .nextRunHandler { invocation ->
+                            didRun.set(true)
+                            assertThat(invocation.annotations.size, `is`(1))
+                            true
+                        }
+                        .forAnnotations(Query::class)
+                        .build())
+                .compilesWithoutError()
+        assertThat(didRun.get(), `is`(true))
+    }
+}
diff --git a/room/compiler/src/test/kotlin/android/arch/persistence/room/testing/TestInvocation.kt b/room/compiler/src/test/kotlin/android/arch/persistence/room/testing/TestInvocation.kt
new file mode 100644
index 0000000..b97628e
--- /dev/null
+++ b/room/compiler/src/test/kotlin/android/arch/persistence/room/testing/TestInvocation.kt
@@ -0,0 +1,32 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.arch.persistence.room.testing
+
+import android.arch.persistence.room.processor.Context
+import javax.annotation.processing.ProcessingEnvironment
+import javax.annotation.processing.RoundEnvironment
+import javax.lang.model.element.TypeElement
+
+data class TestInvocation(val processingEnv: ProcessingEnvironment,
+                          val annotations: MutableSet<out TypeElement>,
+                          val roundEnv: RoundEnvironment) {
+    val context = Context(processingEnv)
+
+    fun typeElement(qName: String) : TypeElement {
+        return processingEnv.elementUtils.getTypeElement(qName)
+    }
+}
diff --git a/room/compiler/src/test/kotlin/android/arch/persistence/room/testing/TestProcessor.kt b/room/compiler/src/test/kotlin/android/arch/persistence/room/testing/TestProcessor.kt
new file mode 100644
index 0000000..16c0048
--- /dev/null
+++ b/room/compiler/src/test/kotlin/android/arch/persistence/room/testing/TestProcessor.kt
@@ -0,0 +1,67 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.arch.persistence.room.testing
+
+import javax.annotation.processing.AbstractProcessor
+import javax.annotation.processing.RoundEnvironment
+import javax.annotation.processing.SupportedSourceVersion
+import javax.lang.model.SourceVersion
+import javax.lang.model.element.TypeElement
+import kotlin.reflect.KClass
+
+@SupportedSourceVersion(SourceVersion.RELEASE_8)// test are compiled w/ J_8
+class TestProcessor(val handlers: List<(TestInvocation) -> Boolean>,
+                    val annotations: MutableSet<String>) : AbstractProcessor() {
+    var count = 0
+    override fun process(annotations: MutableSet<out TypeElement>, roundEnv: RoundEnvironment)
+            : Boolean {
+        return handlers.getOrNull(count++)?.invoke(
+                    TestInvocation(processingEnv, annotations, roundEnv)) ?: true
+    }
+
+    override fun getSupportedAnnotationTypes(): MutableSet<String> {
+        return annotations
+    }
+
+    class Builder {
+        private var handlers = arrayListOf<(TestInvocation) -> Boolean>()
+        private var annotations = mutableSetOf<String>()
+        fun nextRunHandler(f: (TestInvocation) -> Boolean): Builder {
+            handlers.add(f)
+            return this
+        }
+
+        fun forAnnotations(vararg klasses: KClass<*>): Builder {
+            annotations.addAll(klasses.map { it.java.canonicalName })
+            return this
+        }
+
+        fun build(): TestProcessor {
+            if (annotations.isEmpty()) {
+                throw IllegalStateException("must provide at least 1 annotation")
+            }
+            if (handlers.isEmpty()) {
+                throw IllegalStateException("must provide at least 1 handler")
+            }
+            return TestProcessor(handlers, annotations)
+        }
+    }
+
+    companion object {
+        fun builder(): Builder = Builder()
+    }
+}
diff --git a/room/compiler/src/test/kotlin/android/arch/persistence/room/testing/test_util.kt b/room/compiler/src/test/kotlin/android/arch/persistence/room/testing/test_util.kt
new file mode 100644
index 0000000..91d7189
--- /dev/null
+++ b/room/compiler/src/test/kotlin/android/arch/persistence/room/testing/test_util.kt
@@ -0,0 +1,122 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+import android.arch.persistence.room.ColumnInfo
+import android.arch.persistence.room.Embedded
+import android.arch.persistence.room.Entity
+import android.arch.persistence.room.PrimaryKey
+import android.arch.persistence.room.Query
+import android.arch.persistence.room.Relation
+import android.arch.persistence.room.ext.LifecyclesTypeNames
+import android.arch.persistence.room.ext.ReactiveStreamsTypeNames
+import android.arch.persistence.room.ext.RoomRxJava2TypeNames
+import android.arch.persistence.room.ext.RxJava2TypeNames
+import android.arch.persistence.room.processor.EntityProcessor
+import android.arch.persistence.room.solver.CodeGenScope
+import android.arch.persistence.room.testing.TestInvocation
+import android.arch.persistence.room.testing.TestProcessor
+import android.arch.persistence.room.verifier.DatabaseVerifier
+import android.arch.persistence.room.writer.ClassWriter
+import android.arch.persistence.room.writer.EntityCursorConverterWriter
+import com.google.auto.common.MoreElements
+import com.google.common.truth.Truth
+import com.google.testing.compile.CompileTester
+import com.google.testing.compile.JavaFileObjects
+import com.google.testing.compile.JavaSourcesSubjectFactory
+import com.squareup.javapoet.ClassName
+import com.squareup.javapoet.TypeSpec
+import org.mockito.Mockito
+import java.io.File
+import javax.lang.model.element.Element
+import javax.lang.model.element.Modifier
+import javax.tools.JavaFileObject
+
+object COMMON {
+    val USER by lazy {
+        loadJavaCode("common/input/User.java", "foo.bar.User")
+    }
+    val USER_TYPE_NAME by lazy {
+        ClassName.get("foo.bar", "User")
+    }
+    val BOOK by lazy {
+        loadJavaCode("common/input/Book.java", "foo.bar.Book")
+    }
+    val NOT_AN_ENTITY by lazy {
+        loadJavaCode("common/input/NotAnEntity.java", "foo.bar.NotAnEntity")
+    }
+
+    val NOT_AN_ENTITY_TYPE_NAME by lazy {
+        ClassName.get("foo.bar", "NotAnEntity")
+    }
+
+    val MULTI_PKEY_ENTITY by lazy {
+        loadJavaCode("common/input/MultiPKeyEntity.java", "MultiPKeyEntity")
+    }
+    val LIVE_DATA by lazy {
+        loadJavaCode("common/input/LiveData.java", LifecyclesTypeNames.LIVE_DATA.toString())
+    }
+    val COMPUTABLE_LIVE_DATA by lazy {
+        loadJavaCode("common/input/ComputableLiveData.java",
+                LifecyclesTypeNames.COMPUTABLE_LIVE_DATA.toString())
+    }
+    val PUBLISHER by lazy {
+        loadJavaCode("common/input/reactivestreams/Publisher.java",
+                ReactiveStreamsTypeNames.PUBLISHER.toString())
+    }
+    val FLOWABLE by lazy {
+        loadJavaCode("common/input/rxjava2/Flowable.java", RxJava2TypeNames.FLOWABLE.toString())
+    }
+
+    val RX2_ROOM by lazy {
+        loadJavaCode("common/input/Rx2Room.java", RoomRxJava2TypeNames.RX_ROOM.toString())
+    }
+}
+fun testCodeGenScope(): CodeGenScope {
+    return CodeGenScope(Mockito.mock(ClassWriter::class.java))
+}
+
+fun simpleRun(vararg jfos : JavaFileObject, f: (TestInvocation) -> Unit): CompileTester {
+    return Truth.assertAbout(JavaSourcesSubjectFactory.javaSources())
+            .that(jfos.toList() + JavaFileObjects.forSourceString("foo.bar.MyClass",
+                    """
+                    package foo.bar;
+                    abstract public class MyClass {
+                    @android.arch.persistence.room.Query("foo")
+                    abstract public void setFoo(String foo);
+                    }
+                    """))
+            .processedWith(TestProcessor.builder()
+                    .nextRunHandler {
+                        f(it)
+                        true
+                    }
+                    .forAnnotations(Query::class, PrimaryKey::class, Embedded::class,
+                            ColumnInfo::class, Relation::class, Entity::class)
+                    .build())
+}
+
+fun loadJavaCode(fileName : String, qName : String) : JavaFileObject {
+    val contents = File("src/test/data/$fileName").readText(Charsets.UTF_8)
+    return JavaFileObjects.forSourceString(qName, contents)
+}
+
+fun createVerifierFromEntities(invocation: TestInvocation) : DatabaseVerifier {
+    val entities = invocation.roundEnv.getElementsAnnotatedWith(Entity::class.java).map {
+        EntityProcessor(invocation.context, MoreElements.asType(it)).process()
+    }
+    return DatabaseVerifier.create(invocation.context, Mockito.mock(Element::class.java),
+            entities)!!
+}
diff --git a/room/compiler/src/test/kotlin/android/arch/persistence/room/verifier/DatabaseVerifierTest.kt b/room/compiler/src/test/kotlin/android/arch/persistence/room/verifier/DatabaseVerifierTest.kt
new file mode 100644
index 0000000..df8afce
--- /dev/null
+++ b/room/compiler/src/test/kotlin/android/arch/persistence/room/verifier/DatabaseVerifierTest.kt
@@ -0,0 +1,237 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.arch.persistence.room.verifier
+
+import collect
+import columnNames
+import android.arch.persistence.room.parser.SQLTypeAffinity
+import android.arch.persistence.room.processor.Context
+import android.arch.persistence.room.testing.TestInvocation
+import android.arch.persistence.room.vo.CallType
+import android.arch.persistence.room.vo.Constructor
+import android.arch.persistence.room.vo.Database
+import android.arch.persistence.room.vo.Entity
+import android.arch.persistence.room.vo.Field
+import android.arch.persistence.room.vo.FieldGetter
+import android.arch.persistence.room.vo.FieldSetter
+import android.arch.persistence.room.vo.PrimaryKey
+import org.hamcrest.CoreMatchers.`is`
+import org.hamcrest.CoreMatchers.hasItem
+import org.hamcrest.CoreMatchers.notNullValue
+import org.hamcrest.MatcherAssert.assertThat
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.junit.runners.JUnit4
+import org.mockito.Mockito.mock
+import simpleRun
+import java.sql.Connection
+import javax.lang.model.element.Element
+import javax.lang.model.element.ExecutableElement
+import javax.lang.model.element.TypeElement
+import javax.lang.model.type.DeclaredType
+import javax.lang.model.type.PrimitiveType
+import javax.lang.model.type.TypeKind
+import javax.lang.model.type.TypeMirror
+
+@RunWith(JUnit4::class)
+class DatabaseVerifierTest {
+    @Test
+    fun testSimpleDatabase() {
+        simpleRun { invocation ->
+            val verifier = createVerifier(invocation)
+            val stmt = verifier.connection.createStatement()
+            val rs = stmt.executeQuery("select * from sqlite_master WHERE type='table'")
+            assertThat(
+                    rs.collect { set -> set.getString("name") }, hasItem(`is`("User")))
+            val table = verifier.connection.prepareStatement("select * from User")
+            assertThat(table.columnNames(), `is`(listOf("id", "name", "lastName", "ratio")))
+
+            assertThat(getPrimaryKeys(verifier.connection, "User"), `is`(listOf("id")))
+        }.compilesWithoutError()
+    }
+
+    fun createVerifier(invocation: TestInvocation): DatabaseVerifier {
+        return DatabaseVerifier.create(invocation.context, mock(Element::class.java),
+                userDb(invocation.context).entities)!!
+    }
+
+    @Test
+    fun testFullEntityQuery() {
+        validQueryTest("select * from User") {
+            assertThat(it, `is`(
+                    QueryResultInfo(listOf(
+                            ColumnInfo("id", SQLTypeAffinity.INTEGER),
+                            ColumnInfo("name", SQLTypeAffinity.TEXT),
+                            ColumnInfo("lastName", SQLTypeAffinity.TEXT),
+                            ColumnInfo("ratio", SQLTypeAffinity.REAL)
+                    ))))
+        }
+    }
+
+    @Test
+    fun testPartialFields() {
+        validQueryTest("select id, lastName from User") {
+            assertThat(it, `is`(
+                    QueryResultInfo(listOf(
+                            ColumnInfo("id", SQLTypeAffinity.INTEGER),
+                            ColumnInfo("lastName", SQLTypeAffinity.TEXT)
+                    ))))
+        }
+    }
+
+    @Test
+    fun testRenamedField() {
+        validQueryTest("select id as myId, lastName from User") {
+            assertThat(it, `is`(
+                    QueryResultInfo(listOf(
+                            ColumnInfo("myId", SQLTypeAffinity.INTEGER),
+                            ColumnInfo("lastName", SQLTypeAffinity.TEXT)
+                    ))))
+        }
+    }
+
+    @Test
+    fun testGrouped() {
+        validQueryTest("select MAX(ratio) from User GROUP BY name") {
+            assertThat(it, `is`(
+                    QueryResultInfo(listOf(
+                            // unfortunately, we don't get this information
+                            ColumnInfo("MAX(ratio)", SQLTypeAffinity.NULL)
+                    ))))
+        }
+    }
+
+    @Test
+    fun testConcat() {
+        validQueryTest("select name || lastName as mergedName from User") {
+            assertThat(it, `is`(
+                    QueryResultInfo(listOf(
+                            // unfortunately, we don't get this information
+                            ColumnInfo("mergedName", SQLTypeAffinity.NULL)
+                    ))))
+        }
+    }
+
+    @Test
+    fun testResultWithArgs() {
+        validQueryTest("select id, name || lastName as mergedName from User where name LIKE ?") {
+            assertThat(it, `is`(
+                    QueryResultInfo(listOf(
+                            // unfortunately, we don't get this information
+                            ColumnInfo("id", SQLTypeAffinity.INTEGER),
+                            ColumnInfo("mergedName", SQLTypeAffinity.NULL)
+                    ))))
+        }
+    }
+
+    @Test
+    fun testDeleteQuery() {
+        validQueryTest("delete from User where name LIKE ?") {
+            assertThat(it, `is`(QueryResultInfo(emptyList())))
+        }
+    }
+
+    @Test
+    fun testUpdateQuery() {
+        validQueryTest("update User set name = ? WHERE id = ?") {
+            assertThat(it, `is`(QueryResultInfo(emptyList())))
+        }
+    }
+
+    @Test
+    fun testBadQuery() {
+        simpleRun { invocation ->
+            val verifier = createVerifier(invocation)
+            val (columns, error) = verifier.analyze("select foo from User")
+            assertThat(error, notNullValue())
+        }.compilesWithoutError()
+    }
+
+    private fun validQueryTest(sql: String, cb: (QueryResultInfo) -> Unit) {
+        simpleRun { invocation ->
+            val verifier = createVerifier(invocation)
+            val info = verifier.analyze(sql)
+            cb(info)
+        }.compilesWithoutError()
+    }
+
+    private fun userDb(context: Context): Database {
+        return database(entity("User",
+                field("id", primitive(context, TypeKind.INT), SQLTypeAffinity.INTEGER),
+                field("name", context.COMMON_TYPES.STRING, SQLTypeAffinity.TEXT),
+                field("lastName", context.COMMON_TYPES.STRING, SQLTypeAffinity.TEXT),
+                field("ratio", primitive(context, TypeKind.FLOAT), SQLTypeAffinity.REAL)))
+    }
+
+    private fun database(vararg entities: Entity): Database {
+        return Database(
+                element = mock(TypeElement::class.java),
+                type = mock(TypeMirror::class.java),
+                entities = entities.toList(),
+                daoMethods = emptyList(),
+                version = -1,
+                exportSchema = false,
+                enableForeignKeys = false)
+    }
+
+    private fun entity(tableName: String, vararg fields: Field): Entity {
+        return Entity(
+                element = mock(TypeElement::class.java),
+                tableName = tableName,
+                type = mock(DeclaredType::class.java),
+                fields = fields.toList(),
+                embeddedFields = emptyList(),
+                indices = emptyList(),
+                primaryKey = PrimaryKey(null, fields.take(1), false),
+                foreignKeys = emptyList(),
+                constructor = Constructor(mock(ExecutableElement::class.java), emptyList())
+        )
+    }
+
+    private fun field(name: String, type: TypeMirror, affinity: SQLTypeAffinity): Field {
+        val f = Field(
+                element = mock(Element::class.java),
+                name = name,
+                type = type,
+                columnName = name,
+                affinity = affinity
+        )
+        assignGetterSetter(f, name, type)
+        return f
+    }
+
+    private fun assignGetterSetter(f: Field, name: String, type: TypeMirror) {
+        f.getter = FieldGetter(name, type, CallType.FIELD)
+        f.setter = FieldSetter(name, type, CallType.FIELD)
+    }
+
+    private fun primitive(context: Context, kind: TypeKind): PrimitiveType {
+        return context.processingEnv.typeUtils.getPrimitiveType(kind)
+    }
+
+    private fun getPrimaryKeys(connection: Connection, tableName: String): List<String> {
+        val stmt = connection.createStatement()
+        val resultSet = stmt.executeQuery("PRAGMA table_info($tableName)")
+        return resultSet.collect {
+            Pair(it.getString("name"), it.getInt("pk"))
+        }
+                .filter { it.second > 0 }
+                .sortedBy { it.second }
+                .map { it.first }
+
+    }
+}
diff --git a/room/compiler/src/test/kotlin/android/arch/persistence/room/vo/IndexTest.kt b/room/compiler/src/test/kotlin/android/arch/persistence/room/vo/IndexTest.kt
new file mode 100644
index 0000000..29d82d8
--- /dev/null
+++ b/room/compiler/src/test/kotlin/android/arch/persistence/room/vo/IndexTest.kt
@@ -0,0 +1,56 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.arch.persistence.room.vo
+
+import android.arch.persistence.room.parser.SQLTypeAffinity
+import org.hamcrest.CoreMatchers
+import org.hamcrest.MatcherAssert
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.junit.runners.JUnit4
+import org.mockito.Mockito.mock
+import javax.lang.model.element.Element
+import javax.lang.model.type.TypeMirror
+
+@RunWith(JUnit4::class)
+class IndexTest {
+    @Test
+    fun createSimpleSQL() {
+        val index = Index("foo", false, listOf(mockField("bar"), mockField("baz")))
+        MatcherAssert.assertThat(index.createQuery("my_table"), CoreMatchers.`is`(
+                "CREATE  INDEX `foo` ON `my_table` (`bar`, `baz`)"
+        ))
+    }
+
+    @Test
+    fun createUnique() {
+        val index = Index("foo", true, listOf(mockField("bar"), mockField("baz")))
+        MatcherAssert.assertThat(index.createQuery("my_table"), CoreMatchers.`is`(
+                "CREATE UNIQUE INDEX `foo` ON `my_table` (`bar`, `baz`)"
+        ))
+    }
+
+    private fun mockField(columnName : String): Field {
+        return Field(
+                element = mock(Element::class.java),
+                name = columnName + "_field",
+                affinity = SQLTypeAffinity.TEXT,
+                type = mock(TypeMirror::class.java),
+                columnName = columnName
+        )
+    }
+}
diff --git a/room/compiler/src/test/kotlin/android/arch/persistence/room/writer/DaoWriterTest.kt b/room/compiler/src/test/kotlin/android/arch/persistence/room/writer/DaoWriterTest.kt
new file mode 100644
index 0000000..6d2bda3
--- /dev/null
+++ b/room/compiler/src/test/kotlin/android/arch/persistence/room/writer/DaoWriterTest.kt
@@ -0,0 +1,108 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.arch.persistence.room.writer
+
+import COMMON
+import android.arch.persistence.room.ext.RoomTypeNames
+import android.arch.persistence.room.processor.DaoProcessor
+import android.arch.persistence.room.testing.TestProcessor
+import com.google.auto.common.MoreElements
+import com.google.auto.common.MoreTypes
+import com.google.common.truth.Truth
+import com.google.testing.compile.CompileTester
+import com.google.testing.compile.JavaSourcesSubjectFactory
+import createVerifierFromEntities
+import loadJavaCode
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.junit.runners.JUnit4
+import javax.tools.JavaFileObject
+
+@RunWith(JUnit4::class)
+class DaoWriterTest {
+    @Test
+    fun complexDao() {
+        singleDao(
+                loadJavaCode("databasewriter/input/ComplexDatabase.java",
+                        "foo.bar.ComplexDatabase"),
+                loadJavaCode("daoWriter/input/ComplexDao.java", "foo.bar.ComplexDao")
+        ).compilesWithoutError().and().generatesSources(
+                loadJavaCode("daoWriter/output/ComplexDao.java", "foo.bar.ComplexDao_Impl")
+        )
+    }
+
+    @Test
+    fun writerDao() {
+        singleDao(
+                loadJavaCode("daoWriter/input/WriterDao.java", "foo.bar.WriterDao")
+        ).compilesWithoutError().and().generatesSources(
+                loadJavaCode("daoWriter/output/WriterDao.java", "foo.bar.WriterDao_Impl")
+        )
+    }
+
+    @Test
+    fun deletionDao() {
+        singleDao(
+                loadJavaCode("daoWriter/input/DeletionDao.java", "foo.bar.DeletionDao")
+        ).compilesWithoutError().and().generatesSources(
+                loadJavaCode("daoWriter/output/DeletionDao.java", "foo.bar.DeletionDao_Impl")
+        )
+    }
+
+    @Test
+    fun updateDao() {
+        singleDao(
+                loadJavaCode("daoWriter/input/UpdateDao.java", "foo.bar.UpdateDao")
+        ).compilesWithoutError().and().generatesSources(
+                loadJavaCode("daoWriter/output/UpdateDao.java", "foo.bar.UpdateDao_Impl")
+        )
+    }
+
+    fun singleDao(vararg jfo : JavaFileObject): CompileTester {
+        return Truth.assertAbout(JavaSourcesSubjectFactory.javaSources())
+                .that(jfo.toList() + COMMON.USER + COMMON.MULTI_PKEY_ENTITY + COMMON.BOOK +
+                        COMMON.LIVE_DATA + COMMON.COMPUTABLE_LIVE_DATA)
+                .processedWith(TestProcessor.builder()
+                        .forAnnotations(android.arch.persistence.room.Dao::class)
+                        .nextRunHandler { invocation ->
+                            val dao = invocation.roundEnv
+                                    .getElementsAnnotatedWith(
+                                            android.arch.persistence.room.Dao::class.java)
+                                    .first()
+                            val db = invocation.roundEnv
+                                    .getElementsAnnotatedWith(
+                                            android.arch.persistence.room.Database::class.java)
+                                    .firstOrNull()
+                            val dbType = MoreTypes.asDeclared(if (db != null) {
+                                db.asType()
+                            } else {
+                                invocation.context.processingEnv.elementUtils
+                                        .getTypeElement(RoomTypeNames.ROOM_DB.toString()).asType()
+                            })
+                            val parser = DaoProcessor(
+                                    baseContext = invocation.context,
+                                    element = MoreElements.asType(dao),
+                                    dbType = dbType,
+                                    dbVerifier = createVerifierFromEntities(invocation))
+                            val parsedDao = parser.process()
+                            DaoWriter(parsedDao, invocation.processingEnv)
+                                    .write(invocation.processingEnv)
+                            true
+                        }
+                        .build())
+    }
+}
diff --git a/room/compiler/src/test/kotlin/android/arch/persistence/room/writer/DatabaseWriterTest.kt b/room/compiler/src/test/kotlin/android/arch/persistence/room/writer/DatabaseWriterTest.kt
new file mode 100644
index 0000000..9a3252f
--- /dev/null
+++ b/room/compiler/src/test/kotlin/android/arch/persistence/room/writer/DatabaseWriterTest.kt
@@ -0,0 +1,50 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.arch.persistence.room.writer
+
+import COMMON
+import android.arch.persistence.room.RoomProcessor
+import com.google.common.truth.Truth
+import com.google.testing.compile.CompileTester
+import com.google.testing.compile.JavaSourcesSubjectFactory
+import loadJavaCode
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.junit.runners.JUnit4
+import javax.tools.JavaFileObject
+
+@RunWith(JUnit4::class)
+class DatabaseWriterTest {
+    @Test
+    fun simpleDb() {
+        singleDb(
+                loadJavaCode("databasewriter/input/ComplexDatabase.java",
+                        "foo.bar.ComplexDatabase"),
+                loadJavaCode("daoWriter/input/ComplexDao.java",
+                        "foo.bar.ComplexDao")
+        ).compilesWithoutError().and().generatesSources(
+                loadJavaCode("databasewriter/output/ComplexDatabase.java",
+                        "foo.bar.ComplexDatabase_Impl")
+        )
+    }
+
+    private fun singleDb(vararg jfo : JavaFileObject): CompileTester {
+        return Truth.assertAbout(JavaSourcesSubjectFactory.javaSources())
+                .that(jfo.toList() + COMMON.USER +  COMMON.LIVE_DATA + COMMON.COMPUTABLE_LIVE_DATA)
+                .processedWith(RoomProcessor())
+    }
+}
diff --git a/room/compiler/src/test/kotlin/android/arch/persistence/room/writer/EntityCursorConverterWriterTest.kt b/room/compiler/src/test/kotlin/android/arch/persistence/room/writer/EntityCursorConverterWriterTest.kt
new file mode 100644
index 0000000..aaa6b2e
--- /dev/null
+++ b/room/compiler/src/test/kotlin/android/arch/persistence/room/writer/EntityCursorConverterWriterTest.kt
@@ -0,0 +1,103 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.arch.persistence.room.writer
+
+import android.arch.persistence.room.processor.BaseEntityParserTest
+import com.google.testing.compile.CompileTester
+import com.google.testing.compile.JavaFileObjects
+import com.squareup.javapoet.ClassName
+import com.squareup.javapoet.TypeSpec
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.junit.runners.JUnit4
+import javax.lang.model.element.Modifier
+
+@RunWith(JUnit4::class)
+class EntityCursorConverterWriterTest : BaseEntityParserTest() {
+    companion object {
+        val OUT_PREFIX = """
+            package foo.bar;
+            import android.database.Cursor;
+            public class MyContainerClass {
+            """.trimIndent()
+        const val OUT_SUFFIX = "}"
+    }
+
+    @Test
+    fun generateSimple() {
+        generateAndMatch(
+                """
+                @PrimaryKey
+                private int id;
+                String name;
+                String lastName;
+                int age;
+                public int getId() { return id; }
+                public void setId(int id) { this.id = id; }
+                """,
+                """
+                private MyEntity __entityCursorConverter_fooBarMyEntity(Cursor cursor) {
+                  final MyEntity _entity;
+                  final int _cursorIndexOfId = cursor.getColumnIndex("id");
+                  final int _cursorIndexOfName = cursor.getColumnIndex("name");
+                  final int _cursorIndexOfLastName = cursor.getColumnIndex("lastName");
+                  final int _cursorIndexOfAge = cursor.getColumnIndex("age");
+                  _entity = new MyEntity();
+                  if (_cursorIndexOfId != -1) {
+                    final int _tmpId;
+                    _tmpId = cursor.getInt(_cursorIndexOfId);
+                    _entity.setId(_tmpId);
+                  }
+                  if (_cursorIndexOfName != -1) {
+                    _entity.name = cursor.getString(_cursorIndexOfName);
+                  }
+                  if (_cursorIndexOfLastName != -1) {
+                    _entity.lastName = cursor.getString(_cursorIndexOfLastName);
+                  }
+                  if (_cursorIndexOfAge != -1) {
+                    _entity.age = cursor.getInt(_cursorIndexOfAge);
+                  }
+                  return _entity;
+                }
+                """.trimIndent())
+    }
+
+    fun generateAndMatch(input: String, output : String,
+                         attributes: Map<String, String> = mapOf()) {
+        generate(input, attributes)
+                .compilesWithoutError()
+                .and()
+                .generatesSources(JavaFileObjects.forSourceString(
+                        "foo.bar.MyEntity_CursorConverter",
+                        listOf(OUT_PREFIX,output,OUT_SUFFIX).joinToString("\n")))
+    }
+
+    fun generate(input: String, attributes: Map<String, String> = mapOf()) : CompileTester {
+        return singleEntity(input, attributes) { entity, invocation ->
+            val className = ClassName.get("foo.bar","MyContainerClass")
+            val writer = object : ClassWriter(className){
+                override fun createTypeSpecBuilder(): TypeSpec.Builder {
+                    getOrCreateMethod(EntityCursorConverterWriter(entity))
+                    return TypeSpec.classBuilder(className).apply {
+                        addModifiers(Modifier.PUBLIC)
+                    }
+                }
+            }
+            writer.write(invocation.processingEnv)
+        }
+    }
+}
diff --git a/room/compiler/src/test/kotlin/android/arch/persistence/room/writer/SQLiteOpenHelperWriterTest.kt b/room/compiler/src/test/kotlin/android/arch/persistence/room/writer/SQLiteOpenHelperWriterTest.kt
new file mode 100644
index 0000000..b3fe832
--- /dev/null
+++ b/room/compiler/src/test/kotlin/android/arch/persistence/room/writer/SQLiteOpenHelperWriterTest.kt
@@ -0,0 +1,132 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.arch.persistence.room.writer
+
+import android.arch.persistence.room.processor.DatabaseProcessor
+import android.arch.persistence.room.testing.TestInvocation
+import android.arch.persistence.room.testing.TestProcessor
+import android.arch.persistence.room.vo.Database
+import com.google.auto.common.MoreElements
+import com.google.common.truth.Truth
+import com.google.testing.compile.CompileTester
+import com.google.testing.compile.JavaFileObjects
+import com.google.testing.compile.JavaSourcesSubjectFactory
+import org.hamcrest.CoreMatchers.`is`
+import org.hamcrest.MatcherAssert.assertThat
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.junit.runners.JUnit4
+
+@RunWith(JUnit4::class)
+class SQLiteOpenHelperWriterTest {
+    companion object {
+        const val ENTITY_PREFIX = """
+            package foo.bar;
+            import android.arch.persistence.room.*;
+            @Entity%s
+            public class MyEntity {
+            """
+        const val ENTITY_SUFFIX = "}"
+        const val DATABASE_CODE = """
+            package foo.bar;
+            import android.arch.persistence.room.*;
+            @Database(entities = {MyEntity.class}, version = 3)
+            abstract public class MyDatabase extends RoomDatabase {
+            }
+            """
+    }
+
+    @Test
+    fun createSimpleEntity() {
+        singleEntity(
+                """
+                @PrimaryKey
+                String uuid;
+                String name;
+                int age;
+                """.trimIndent()
+        ) { database, invocation ->
+            val query = SQLiteOpenHelperWriter(database)
+                    .createQuery(database.entities.first())
+            assertThat(query, `is`("CREATE TABLE IF NOT EXISTS" +
+                    " `MyEntity` (`uuid` TEXT, `name` TEXT, `age` INTEGER, PRIMARY KEY(`uuid`))"))
+        }.compilesWithoutError()
+    }
+
+    @Test
+    fun multiplePrimaryKeys() {
+        singleEntity(
+                """
+                String uuid;
+                String name;
+                int age;
+                """.trimIndent(), attributes = mapOf("primaryKeys" to "{\"uuid\", \"name\"}")
+        ) { database, invocation ->
+            val query = SQLiteOpenHelperWriter(database)
+                    .createQuery(database.entities.first())
+            assertThat(query, `is`("CREATE TABLE IF NOT EXISTS" +
+                    " `MyEntity` (`uuid` TEXT, `name` TEXT, `age` INTEGER," +
+                    " PRIMARY KEY(`uuid`, `name`))"))
+        }.compilesWithoutError()
+    }
+
+    @Test
+    fun autoIncrement() {
+        singleEntity(
+                """
+                @PrimaryKey(autoGenerate = true)
+                int uuid;
+                String name;
+                int age;
+                """.trimIndent()
+        ) { database, invocation ->
+            val query = SQLiteOpenHelperWriter(database)
+                    .createQuery(database.entities.first())
+            assertThat(query, `is`("CREATE TABLE IF NOT EXISTS" +
+                    " `MyEntity` (`uuid` INTEGER PRIMARY KEY AUTOINCREMENT," +
+                    " `name` TEXT, `age` INTEGER)"))
+        }.compilesWithoutError()
+    }
+
+    fun singleEntity(input: String, attributes: Map<String, String> = mapOf(),
+                     handler: (Database, TestInvocation) -> Unit): CompileTester {
+        val attributesReplacement : String
+        if (attributes.isEmpty()) {
+            attributesReplacement = ""
+        } else {
+            attributesReplacement = "(" +
+                    attributes.entries.map { "${it.key} = ${it.value}" }.joinToString(",") +
+                    ")".trimIndent()
+        }
+        return Truth.assertAbout(JavaSourcesSubjectFactory.javaSources())
+                .that(listOf(JavaFileObjects.forSourceString("foo.bar.MyEntity",
+                        ENTITY_PREFIX.format(attributesReplacement) + input + ENTITY_SUFFIX
+                ), JavaFileObjects.forSourceString("foo.bar.MyDatabase",
+                        DATABASE_CODE)))
+                .processedWith(TestProcessor.builder()
+                        .forAnnotations(android.arch.persistence.room.Database::class)
+                        .nextRunHandler { invocation ->
+                            val db = MoreElements.asType(invocation.roundEnv
+                                    .getElementsAnnotatedWith(
+                                            android.arch.persistence.room.Database::class.java)
+                                    .first())
+                            handler(DatabaseProcessor(invocation.context, db).process(), invocation)
+                            true
+                        }
+                        .build())
+    }
+}
diff --git a/room/db-impl/.gitignore b/room/db-impl/.gitignore
new file mode 100644
index 0000000..796b96d
--- /dev/null
+++ b/room/db-impl/.gitignore
@@ -0,0 +1 @@
+/build
diff --git a/room/db-impl/build.gradle b/room/db-impl/build.gradle
new file mode 100644
index 0000000..978681d
--- /dev/null
+++ b/room/db-impl/build.gradle
@@ -0,0 +1,65 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+import com.android.builder.core.BuilderConstants
+apply plugin: 'com.android.library'
+apply plugin: 'maven'
+
+android {
+    compileSdkVersion tools.current_sdk
+    buildToolsVersion tools.build_tools_version
+
+    defaultConfig {
+        minSdkVersion flatfoot.min_sdk
+        targetSdkVersion tools.current_sdk
+        versionCode 1
+        versionName "1.0"
+
+        testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
+
+    }
+    buildTypes {
+        release {
+            minifyEnabled false
+            proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
+        }
+    }
+
+    testOptions {
+        unitTests.returnDefaultValues = true
+    }
+    compileOptions {
+        sourceCompatibility JavaVersion.VERSION_1_7
+        targetCompatibility JavaVersion.VERSION_1_7
+    }
+}
+
+archivesBaseName = "support-db-impl"
+
+dependencies {
+    compile libs.support.annotations
+    compile project(":room:db")
+}
+createAndroidCheckstyle(project)
+
+android.libraryVariants.all { variant ->
+    def name = variant.buildType.name
+    def suffix = name.capitalize()
+    def jarTask = project.tasks.create(name: "jar${suffix}", type: Jar){
+        dependsOn variant.javaCompile
+        from variant.javaCompile.destinationDir
+        destinationDir new File(project.buildDir, "libJar")
+    }
+}
diff --git a/room/db-impl/proguard-rules.pro b/room/db-impl/proguard-rules.pro
new file mode 100644
index 0000000..b7210d1
--- /dev/null
+++ b/room/db-impl/proguard-rules.pro
@@ -0,0 +1,17 @@
+# Add project specific ProGuard rules here.
+# By default, the flags in this file are appended to flags specified
+# in /Users/yboyar/android/sdk/tools/proguard/proguard-android.txt
+# You can edit the include path and order by changing the proguardFiles
+# directive in build.gradle.
+#
+# For more details, see
+#   http://developer.android.com/guide/developing/tools/proguard.html
+
+# Add any project specific keep options here:
+
+# If your project uses WebView with JS, uncomment the following
+# and specify the fully qualified class name to the JavaScript interface
+# class:
+#-keepclassmembers class fqcn.of.javascript.interface.for.webview {
+#   public *;
+#}
diff --git a/room/db-impl/src/main/AndroidManifest.xml b/room/db-impl/src/main/AndroidManifest.xml
new file mode 100644
index 0000000..9350b90
--- /dev/null
+++ b/room/db-impl/src/main/AndroidManifest.xml
@@ -0,0 +1,19 @@
+<!--
+  ~ Copyright (C) 2016 The Android Open Source Project
+  ~
+  ~ Licensed under the Apache License, Version 2.0 (the "License");
+  ~ you may not use this file except in compliance with the License.
+  ~ You may obtain a copy of the License at
+  ~
+  ~      http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing, software
+  ~ distributed under the License is distributed on an "AS IS" BASIS,
+  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  ~ See the License for the specific language governing permissions and
+  ~ limitations under the License.
+  -->
+
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+          package="android.arch.persistence.db.framework">
+</manifest>
diff --git a/room/db-impl/src/main/java/android/arch/persistence/db/framework/FrameworkSQLiteDatabase.java b/room/db-impl/src/main/java/android/arch/persistence/db/framework/FrameworkSQLiteDatabase.java
new file mode 100644
index 0000000..92a5820
--- /dev/null
+++ b/room/db-impl/src/main/java/android/arch/persistence/db/framework/FrameworkSQLiteDatabase.java
@@ -0,0 +1,320 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.arch.persistence.db.framework;
+
+import android.arch.persistence.db.SimpleSQLiteQuery;
+import android.arch.persistence.db.SupportSQLiteDatabase;
+import android.arch.persistence.db.SupportSQLiteQuery;
+import android.arch.persistence.db.SupportSQLiteStatement;
+import android.content.ContentValues;
+import android.database.Cursor;
+import android.database.SQLException;
+import android.database.sqlite.SQLiteCursor;
+import android.database.sqlite.SQLiteCursorDriver;
+import android.database.sqlite.SQLiteDatabase;
+import android.database.sqlite.SQLiteQuery;
+import android.database.sqlite.SQLiteTransactionListener;
+import android.os.Build;
+import android.os.CancellationSignal;
+import android.support.annotation.RequiresApi;
+import android.util.Pair;
+
+import java.io.IOException;
+import java.util.List;
+import java.util.Locale;
+
+/**
+ * Delegates all calls to an implementation of {@link SQLiteDatabase}.
+ */
+@SuppressWarnings("unused")
+class FrameworkSQLiteDatabase implements SupportSQLiteDatabase {
+    private static final String[] CONFLICT_VALUES = new String[]
+            {"", " OR ROLLBACK ", " OR ABORT ", " OR FAIL ", " OR IGNORE ", " OR REPLACE "};
+    private static final String[] EMPTY_STRING_ARRAY = new String[0];
+
+    private final SQLiteDatabase mDelegate;
+
+    /**
+     * Creates a wrapper around {@link SQLiteDatabase}.
+     *
+     * @param delegate The delegate to receive all calls.
+     */
+    @SuppressWarnings("WeakerAccess")
+    public FrameworkSQLiteDatabase(SQLiteDatabase delegate) {
+        mDelegate = delegate;
+    }
+
+    @Override
+    public SupportSQLiteStatement compileStatement(String sql) {
+        return new FrameworkSQLiteStatement(mDelegate.compileStatement(sql));
+    }
+
+    @Override
+    public void beginTransaction() {
+        mDelegate.beginTransaction();
+    }
+
+    @Override
+    public void beginTransactionNonExclusive() {
+        mDelegate.beginTransactionNonExclusive();
+    }
+
+    @Override
+    public void beginTransactionWithListener(SQLiteTransactionListener transactionListener) {
+        mDelegate.beginTransactionWithListener(transactionListener);
+    }
+
+    @Override
+    public void beginTransactionWithListenerNonExclusive(
+            SQLiteTransactionListener transactionListener) {
+        mDelegate.beginTransactionWithListenerNonExclusive(transactionListener);
+    }
+
+    @Override
+    public void endTransaction() {
+        mDelegate.endTransaction();
+    }
+
+    @Override
+    public void setTransactionSuccessful() {
+        mDelegate.setTransactionSuccessful();
+    }
+
+    @Override
+    public boolean inTransaction() {
+        return mDelegate.inTransaction();
+    }
+
+    @Override
+    public boolean isDbLockedByCurrentThread() {
+        return mDelegate.isDbLockedByCurrentThread();
+    }
+
+    @Override
+    public boolean yieldIfContendedSafely() {
+        return mDelegate.yieldIfContendedSafely();
+    }
+
+    @Override
+    public boolean yieldIfContendedSafely(long sleepAfterYieldDelay) {
+        return mDelegate.yieldIfContendedSafely(sleepAfterYieldDelay);
+    }
+
+    @Override
+    public int getVersion() {
+        return mDelegate.getVersion();
+    }
+
+    @Override
+    public void setVersion(int version) {
+        mDelegate.setVersion(version);
+    }
+
+    @Override
+    public long getMaximumSize() {
+        return mDelegate.getMaximumSize();
+    }
+
+    @Override
+    public long setMaximumSize(long numBytes) {
+        return mDelegate.setMaximumSize(numBytes);
+    }
+
+    @Override
+    public long getPageSize() {
+        return mDelegate.getPageSize();
+    }
+
+    @Override
+    public void setPageSize(long numBytes) {
+        mDelegate.setPageSize(numBytes);
+    }
+
+    @Override
+    public Cursor query(String query) {
+        return query(new SimpleSQLiteQuery(query));
+    }
+
+    @Override
+    public Cursor query(String query, Object[] bindArgs) {
+        return query(new SimpleSQLiteQuery(query, bindArgs));
+    }
+
+
+    @Override
+    public Cursor query(final SupportSQLiteQuery supportQuery) {
+        return mDelegate.rawQueryWithFactory(new SQLiteDatabase.CursorFactory() {
+            @Override
+            public Cursor newCursor(SQLiteDatabase db, SQLiteCursorDriver masterQuery,
+                    String editTable, SQLiteQuery query) {
+                supportQuery.bindTo(new FrameworkSQLiteProgram(query));
+                return new SQLiteCursor(masterQuery, editTable, query);
+            }
+        }, supportQuery.getSql(), EMPTY_STRING_ARRAY, null);
+    }
+
+    @Override
+    @RequiresApi(api = Build.VERSION_CODES.JELLY_BEAN)
+    public Cursor query(final SupportSQLiteQuery supportQuery,
+            CancellationSignal cancellationSignal) {
+        return mDelegate.rawQueryWithFactory(new SQLiteDatabase.CursorFactory() {
+            @Override
+            public Cursor newCursor(SQLiteDatabase db, SQLiteCursorDriver masterQuery,
+                    String editTable, SQLiteQuery query) {
+                supportQuery.bindTo(new FrameworkSQLiteProgram(query));
+                return new SQLiteCursor(masterQuery, editTable, query);
+            }
+        }, supportQuery.getSql(), EMPTY_STRING_ARRAY, null, cancellationSignal);
+    }
+
+    @Override
+    public long insert(String table, int conflictAlgorithm, ContentValues values)
+            throws SQLException {
+        return mDelegate.insertWithOnConflict(table, null, values,
+                conflictAlgorithm);
+    }
+
+    @Override
+    public int delete(String table, String whereClause, Object[] whereArgs) {
+        String query = "DELETE FROM " + table
+                + (isEmpty(whereClause) ? "" : " WHERE " + whereClause);
+        SupportSQLiteStatement statement = compileStatement(query);
+        SimpleSQLiteQuery.bind(statement, whereArgs);
+        return statement.executeUpdateDelete();
+    }
+
+
+    @Override
+    public int update(String table, int conflictAlgorithm, ContentValues values, String whereClause,
+            Object[] whereArgs) {
+        // taken from SQLiteDatabase class.
+        if (values == null || values.size() == 0) {
+            throw new IllegalArgumentException("Empty values");
+        }
+        StringBuilder sql = new StringBuilder(120);
+        sql.append("UPDATE ");
+        sql.append(CONFLICT_VALUES[conflictAlgorithm]);
+        sql.append(table);
+        sql.append(" SET ");
+
+        // move all bind args to one array
+        int setValuesSize = values.size();
+        int bindArgsSize = (whereArgs == null) ? setValuesSize : (setValuesSize + whereArgs.length);
+        Object[] bindArgs = new Object[bindArgsSize];
+        int i = 0;
+        for (String colName : values.keySet()) {
+            sql.append((i > 0) ? "," : "");
+            sql.append(colName);
+            bindArgs[i++] = values.get(colName);
+            sql.append("=?");
+        }
+        if (whereArgs != null) {
+            for (i = setValuesSize; i < bindArgsSize; i++) {
+                bindArgs[i] = whereArgs[i - setValuesSize];
+            }
+        }
+        if (!isEmpty(whereClause)) {
+            sql.append(" WHERE ");
+            sql.append(whereClause);
+        }
+        SupportSQLiteStatement stmt = compileStatement(sql.toString());
+        SimpleSQLiteQuery.bind(stmt, bindArgs);
+        return stmt.executeUpdateDelete();
+    }
+
+    @Override
+    public void execSQL(String sql) throws SQLException {
+        mDelegate.execSQL(sql);
+    }
+
+    @Override
+    public void execSQL(String sql, Object[] bindArgs) throws SQLException {
+        mDelegate.execSQL(sql, bindArgs);
+    }
+
+    @Override
+    public boolean isReadOnly() {
+        return mDelegate.isReadOnly();
+    }
+
+    @Override
+    public boolean isOpen() {
+        return mDelegate.isOpen();
+    }
+
+    @Override
+    public boolean needUpgrade(int newVersion) {
+        return mDelegate.needUpgrade(newVersion);
+    }
+
+    @Override
+    public String getPath() {
+        return mDelegate.getPath();
+    }
+
+    @Override
+    public void setLocale(Locale locale) {
+        mDelegate.setLocale(locale);
+    }
+
+    @Override
+    public void setMaxSqlCacheSize(int cacheSize) {
+        mDelegate.setMaxSqlCacheSize(cacheSize);
+    }
+
+    @Override
+    @RequiresApi(api = Build.VERSION_CODES.JELLY_BEAN)
+    public void setForeignKeyConstraintsEnabled(boolean enable) {
+        mDelegate.setForeignKeyConstraintsEnabled(enable);
+    }
+
+    @Override
+    public boolean enableWriteAheadLogging() {
+        return mDelegate.enableWriteAheadLogging();
+    }
+
+    @Override
+    @RequiresApi(api = Build.VERSION_CODES.JELLY_BEAN)
+    public void disableWriteAheadLogging() {
+        mDelegate.disableWriteAheadLogging();
+    }
+
+    @Override
+    @RequiresApi(api = Build.VERSION_CODES.JELLY_BEAN)
+    public boolean isWriteAheadLoggingEnabled() {
+        return mDelegate.isWriteAheadLoggingEnabled();
+    }
+
+    @Override
+    public List<Pair<String, String>> getAttachedDbs() {
+        return mDelegate.getAttachedDbs();
+    }
+
+    @Override
+    public boolean isDatabaseIntegrityOk() {
+        return mDelegate.isDatabaseIntegrityOk();
+    }
+
+    @Override
+    public void close() throws IOException {
+        mDelegate.close();
+    }
+
+    private static boolean isEmpty(String input) {
+        return input == null || input.length() == 0;
+    }
+}
diff --git a/room/db-impl/src/main/java/android/arch/persistence/db/framework/FrameworkSQLiteOpenHelper.java b/room/db-impl/src/main/java/android/arch/persistence/db/framework/FrameworkSQLiteOpenHelper.java
new file mode 100644
index 0000000..aa08fa4
--- /dev/null
+++ b/room/db-impl/src/main/java/android/arch/persistence/db/framework/FrameworkSQLiteOpenHelper.java
@@ -0,0 +1,128 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.arch.persistence.db.framework;
+
+import android.arch.persistence.db.SupportSQLiteDatabase;
+import android.arch.persistence.db.SupportSQLiteOpenHelper;
+import android.content.Context;
+import android.database.DatabaseErrorHandler;
+import android.database.sqlite.SQLiteDatabase;
+import android.database.sqlite.SQLiteOpenHelper;
+import android.os.Build;
+import android.support.annotation.RequiresApi;
+
+class FrameworkSQLiteOpenHelper implements SupportSQLiteOpenHelper {
+    private final OpenHelper mDelegate;
+
+    FrameworkSQLiteOpenHelper(Context context, String name, int version,
+            DatabaseErrorHandler errorHandler,
+            SupportSQLiteOpenHelper.Callback callback) {
+        mDelegate = createDelegate(context, name, version, errorHandler, callback);
+    }
+
+    private OpenHelper createDelegate(Context context, String name,
+            int version, DatabaseErrorHandler errorHandler,
+            final Callback callback) {
+        return new OpenHelper(context, name, null, version, errorHandler) {
+            @Override
+            public void onCreate(SQLiteDatabase sqLiteDatabase) {
+                mWrappedDb = new FrameworkSQLiteDatabase(sqLiteDatabase);
+                callback.onCreate(mWrappedDb);
+            }
+
+            @Override
+            public void onUpgrade(SQLiteDatabase sqLiteDatabase, int oldVersion, int newVersion) {
+                callback.onUpgrade(getWrappedDb(sqLiteDatabase), oldVersion, newVersion);
+            }
+
+            @Override
+            public void onConfigure(SQLiteDatabase db) {
+                callback.onConfigure(getWrappedDb(db));
+            }
+
+            @Override
+            public void onDowngrade(SQLiteDatabase db, int oldVersion, int newVersion) {
+                callback.onDowngrade(getWrappedDb(db), oldVersion, newVersion);
+            }
+
+            @Override
+            public void onOpen(SQLiteDatabase db) {
+                callback.onOpen(getWrappedDb(db));
+            }
+        };
+    }
+
+    @Override
+    public String getDatabaseName() {
+        return mDelegate.getDatabaseName();
+    }
+
+    @Override
+    @RequiresApi(api = Build.VERSION_CODES.JELLY_BEAN)
+    public void setWriteAheadLoggingEnabled(boolean enabled) {
+        mDelegate.setWriteAheadLoggingEnabled(enabled);
+    }
+
+    @Override
+    public SupportSQLiteDatabase getWritableDatabase() {
+        return mDelegate.getWritableSupportDatabase();
+    }
+
+    @Override
+    public SupportSQLiteDatabase getReadableDatabase() {
+        return mDelegate.getReadableSupportDatabase();
+    }
+
+    @Override
+    public void close() {
+        mDelegate.close();
+    }
+
+    abstract static class OpenHelper extends SQLiteOpenHelper {
+
+        FrameworkSQLiteDatabase mWrappedDb;
+
+        OpenHelper(Context context, String name,
+                SQLiteDatabase.CursorFactory factory, int version,
+                DatabaseErrorHandler errorHandler) {
+            super(context, name, factory, version, errorHandler);
+        }
+
+        SupportSQLiteDatabase getWritableSupportDatabase() {
+            SQLiteDatabase db = super.getWritableDatabase();
+            return getWrappedDb(db);
+        }
+
+        SupportSQLiteDatabase getReadableSupportDatabase() {
+            SQLiteDatabase db = super.getReadableDatabase();
+            return getWrappedDb(db);
+        }
+
+        FrameworkSQLiteDatabase getWrappedDb(SQLiteDatabase sqLiteDatabase) {
+            if (mWrappedDb == null) {
+                mWrappedDb = new FrameworkSQLiteDatabase(sqLiteDatabase);
+            }
+            return mWrappedDb;
+        }
+
+        @Override
+        public synchronized void close() {
+            super.close();
+            mWrappedDb = null;
+        }
+    }
+}
diff --git a/room/db-impl/src/main/java/android/arch/persistence/db/framework/FrameworkSQLiteOpenHelperFactory.java b/room/db-impl/src/main/java/android/arch/persistence/db/framework/FrameworkSQLiteOpenHelperFactory.java
new file mode 100644
index 0000000..7b4245b
--- /dev/null
+++ b/room/db-impl/src/main/java/android/arch/persistence/db/framework/FrameworkSQLiteOpenHelperFactory.java
@@ -0,0 +1,34 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.arch.persistence.db.framework;
+
+import android.arch.persistence.db.SupportSQLiteOpenHelper;
+
+/**
+ * Implements {@link SupportSQLiteOpenHelper.Factory} using the SQLite implementation in the
+ * framework.
+ */
+@SuppressWarnings("unused")
+public class FrameworkSQLiteOpenHelperFactory implements SupportSQLiteOpenHelper.Factory {
+    @Override
+    public SupportSQLiteOpenHelper create(SupportSQLiteOpenHelper.Configuration configuration) {
+        return new FrameworkSQLiteOpenHelper(
+                configuration.context, configuration.name,
+                configuration.version, configuration.errorHandler, configuration.callback
+        );
+    }
+}
diff --git a/room/db-impl/src/main/java/android/arch/persistence/db/framework/FrameworkSQLiteProgram.java b/room/db-impl/src/main/java/android/arch/persistence/db/framework/FrameworkSQLiteProgram.java
new file mode 100644
index 0000000..500f9b9
--- /dev/null
+++ b/room/db-impl/src/main/java/android/arch/persistence/db/framework/FrameworkSQLiteProgram.java
@@ -0,0 +1,61 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.arch.persistence.db.framework;
+
+import android.arch.persistence.db.SupportSQLiteProgram;
+import android.database.sqlite.SQLiteProgram;
+
+/**
+ * An wrapper around {@link SQLiteProgram} to implement {@link SupportSQLiteProgram} API.
+ */
+class FrameworkSQLiteProgram implements SupportSQLiteProgram {
+    private final SQLiteProgram mDelegate;
+
+    FrameworkSQLiteProgram(SQLiteProgram delegate) {
+        mDelegate = delegate;
+    }
+
+    @Override
+    public void bindNull(int index) {
+        mDelegate.bindNull(index);
+    }
+
+    @Override
+    public void bindLong(int index, long value) {
+        mDelegate.bindLong(index, value);
+    }
+
+    @Override
+    public void bindDouble(int index, double value) {
+        mDelegate.bindDouble(index, value);
+    }
+
+    @Override
+    public void bindString(int index, String value) {
+        mDelegate.bindString(index, value);
+    }
+
+    @Override
+    public void bindBlob(int index, byte[] value) {
+        mDelegate.bindBlob(index, value);
+    }
+
+    @Override
+    public void clearBindings() {
+        mDelegate.clearBindings();
+    }
+}
diff --git a/room/db-impl/src/main/java/android/arch/persistence/db/framework/FrameworkSQLiteStatement.java b/room/db-impl/src/main/java/android/arch/persistence/db/framework/FrameworkSQLiteStatement.java
new file mode 100644
index 0000000..65dd49f
--- /dev/null
+++ b/room/db-impl/src/main/java/android/arch/persistence/db/framework/FrameworkSQLiteStatement.java
@@ -0,0 +1,92 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.arch.persistence.db.framework;
+
+import android.arch.persistence.db.SupportSQLiteStatement;
+import android.database.sqlite.SQLiteStatement;
+
+/**
+ * Delegates all calls to a {@link SQLiteStatement}.
+ */
+class FrameworkSQLiteStatement implements SupportSQLiteStatement {
+    private final SQLiteStatement mDelegate;
+
+    /**
+     * Creates a wrapper around a framework {@link SQLiteStatement}.
+     *
+     * @param delegate The SQLiteStatement to delegate calls to.
+     */
+    @SuppressWarnings("WeakerAccess")
+    public FrameworkSQLiteStatement(SQLiteStatement delegate) {
+        mDelegate = delegate;
+    }
+
+    @Override
+    public void bindNull(int index) {
+        mDelegate.bindNull(index);
+    }
+
+    @Override
+    public void bindLong(int index, long value) {
+        mDelegate.bindLong(index, value);
+    }
+
+    @Override
+    public void bindDouble(int index, double value) {
+        mDelegate.bindDouble(index, value);
+    }
+
+    @Override
+    public void bindString(int index, String value) {
+        mDelegate.bindString(index, value);
+    }
+
+    @Override
+    public void bindBlob(int index, byte[] value) {
+        mDelegate.bindBlob(index, value);
+    }
+
+    @Override
+    public void clearBindings() {
+        mDelegate.clearBindings();
+    }
+
+    @Override
+    public void execute() {
+        mDelegate.execute();
+    }
+
+    @Override
+    public int executeUpdateDelete() {
+        return mDelegate.executeUpdateDelete();
+    }
+
+    @Override
+    public long executeInsert() {
+        return mDelegate.executeInsert();
+    }
+
+    @Override
+    public long simpleQueryForLong() {
+        return mDelegate.simpleQueryForLong();
+    }
+
+    @Override
+    public String simpleQueryForString() {
+        return mDelegate.simpleQueryForString();
+    }
+}
diff --git a/room/db/build.gradle b/room/db/build.gradle
new file mode 100644
index 0000000..d495bb8
--- /dev/null
+++ b/room/db/build.gradle
@@ -0,0 +1,64 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+import com.android.builder.core.BuilderConstants
+apply plugin: 'com.android.library'
+apply plugin: 'maven'
+
+android {
+    compileSdkVersion tools.current_sdk
+    buildToolsVersion tools.build_tools_version
+
+    defaultConfig {
+        minSdkVersion flatfoot.min_sdk
+        targetSdkVersion tools.current_sdk
+        versionCode 1
+        versionName "1.0"
+
+        testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
+
+    }
+    buildTypes {
+        release {
+            minifyEnabled false
+            proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
+        }
+    }
+
+    testOptions {
+        unitTests.returnDefaultValues = true
+    }
+    compileOptions {
+        sourceCompatibility JavaVersion.VERSION_1_7
+        targetCompatibility JavaVersion.VERSION_1_7
+    }
+}
+
+archivesBaseName = "support-db"
+
+dependencies {
+    compile libs.support.annotations
+}
+createAndroidCheckstyle(project)
+
+android.libraryVariants.all { variant ->
+    def name = variant.buildType.name
+    def suffix = name.capitalize()
+    def jarTask = project.tasks.create(name: "jar${suffix}", type: Jar){
+        dependsOn variant.javaCompile
+        from variant.javaCompile.destinationDir
+        destinationDir new File(project.buildDir, "libJar")
+    }
+}
diff --git a/room/db/src/main/AndroidManifest.xml b/room/db/src/main/AndroidManifest.xml
new file mode 100644
index 0000000..8a27324
--- /dev/null
+++ b/room/db/src/main/AndroidManifest.xml
@@ -0,0 +1,19 @@
+<!--
+  ~ Copyright (C) 2016 The Android Open Source Project
+  ~
+  ~ Licensed under the Apache License, Version 2.0 (the "License");
+  ~ you may not use this file except in compliance with the License.
+  ~ You may obtain a copy of the License at
+  ~
+  ~      http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing, software
+  ~ distributed under the License is distributed on an "AS IS" BASIS,
+  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  ~ See the License for the specific language governing permissions and
+  ~ limitations under the License.
+  -->
+
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+          package="android.arch.persistence.db">
+</manifest>
diff --git a/room/db/src/main/java/android/arch/persistence/db/SimpleSQLiteQuery.java b/room/db/src/main/java/android/arch/persistence/db/SimpleSQLiteQuery.java
new file mode 100644
index 0000000..b821176
--- /dev/null
+++ b/room/db/src/main/java/android/arch/persistence/db/SimpleSQLiteQuery.java
@@ -0,0 +1,100 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.arch.persistence.db;
+
+/**
+ * A basic implemtation of {@link SupportSQLiteQuery} which receives a query and its args and binds
+ * args based on the passed in Object type.
+ */
+public class SimpleSQLiteQuery implements SupportSQLiteQuery {
+    private final String mQuery;
+    private final Object[] mBindArgs;
+
+    /**
+     * Creates an SQL query with the sql string and the bind arguments.
+     *
+     * @param query    The query string, can include bind arguments (.e.g ?).
+     * @param bindArgs The bind argument value that will replace the placeholders in the query.
+     */
+    public SimpleSQLiteQuery(String query, Object[] bindArgs) {
+        mQuery = query;
+        mBindArgs = bindArgs;
+    }
+
+    /**
+     * Creates an SQL query without any bind arguments.
+     *
+     * @param query The SQL query to execute. Cannot include bind parameters.
+     */
+    public SimpleSQLiteQuery(String query) {
+        this(query, null);
+    }
+
+    @Override
+    public String getSql() {
+        return mQuery;
+    }
+
+    @Override
+    public void bindTo(SupportSQLiteProgram statement) {
+        bind(statement, mBindArgs);
+    }
+
+    /**
+     * Binds the given arguments into the given sqlite statement.
+     *
+     * @param statement The sqlite statement
+     * @param bindArgs  The list of bind arguments
+     */
+    public static void bind(SupportSQLiteProgram statement, Object[] bindArgs) {
+        if (bindArgs == null) {
+            return;
+        }
+        final int limit = bindArgs.length;
+        for (int i = 0; i < limit; i++) {
+            final Object arg = bindArgs[i];
+            bind(statement, i + 1, arg);
+        }
+    }
+
+    private static void bind(SupportSQLiteProgram statement, int index, Object arg) {
+        // extracted from android.database.sqlite.SQLiteConnection
+        if (arg == null) {
+            statement.bindNull(index);
+        } else if (arg instanceof byte[]) {
+            statement.bindBlob(index, (byte[]) arg);
+        } else if (arg instanceof Float) {
+            statement.bindDouble(index, (Float) arg);
+        } else if (arg instanceof Double) {
+            statement.bindDouble(index, (Double) arg);
+        } else if (arg instanceof Long) {
+            statement.bindLong(index, (Long) arg);
+        } else if (arg instanceof Integer) {
+            statement.bindLong(index, (Integer) arg);
+        } else if (arg instanceof Short) {
+            statement.bindLong(index, (Short) arg);
+        } else if (arg instanceof Byte) {
+            statement.bindLong(index, (Byte) arg);
+        } else if (arg instanceof String) {
+            statement.bindString(index, (String) arg);
+        } else {
+            throw new IllegalArgumentException("Cannot bind " + arg + " at index " + index
+                    + " Supported types: null, byte[], float, double, long, int, short, byte,"
+                    + " string");
+        }
+    }
+}
diff --git a/room/db/src/main/java/android/arch/persistence/db/SupportSQLiteDatabase.java b/room/db/src/main/java/android/arch/persistence/db/SupportSQLiteDatabase.java
new file mode 100644
index 0000000..5f71baf
--- /dev/null
+++ b/room/db/src/main/java/android/arch/persistence/db/SupportSQLiteDatabase.java
@@ -0,0 +1,605 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.arch.persistence.db;
+
+import android.content.ContentValues;
+import android.database.Cursor;
+import android.database.SQLException;
+import android.database.sqlite.SQLiteDatabase;
+import android.database.sqlite.SQLiteTransactionListener;
+import android.os.Build;
+import android.os.CancellationSignal;
+import android.os.OperationCanceledException;
+import android.support.annotation.RequiresApi;
+import android.util.Pair;
+
+import java.io.Closeable;
+import java.util.List;
+import java.util.Locale;
+
+/**
+ * A database abstraction which removes the framework dependency and allows swapping underlying
+ * sql versions. It mimics the behavior of {@link android.database.sqlite.SQLiteDatabase}
+ */
+@SuppressWarnings("unused")
+public interface SupportSQLiteDatabase extends Closeable {
+    /**
+     * Compiles the given SQL statement.
+     *
+     * @param sql The sql query.
+     * @return Compiled statement.
+     */
+    SupportSQLiteStatement compileStatement(String sql);
+
+    /**
+     * Begins a transaction in EXCLUSIVE mode.
+     * <p>
+     * Transactions can be nested.
+     * When the outer transaction is ended all of
+     * the work done in that transaction and all of the nested transactions will be committed or
+     * rolled back. The changes will be rolled back if any transaction is ended without being
+     * marked as clean (by calling setTransactionSuccessful). Otherwise they will be committed.
+     * </p>
+     * <p>Here is the standard idiom for transactions:
+     *
+     * <pre>
+     *   db.beginTransaction();
+     *   try {
+     *     ...
+     *     db.setTransactionSuccessful();
+     *   } finally {
+     *     db.endTransaction();
+     *   }
+     * </pre>
+     */
+    void beginTransaction();
+
+    /**
+     * Begins a transaction in IMMEDIATE mode. Transactions can be nested. When
+     * the outer transaction is ended all of the work done in that transaction
+     * and all of the nested transactions will be committed or rolled back. The
+     * changes will be rolled back if any transaction is ended without being
+     * marked as clean (by calling setTransactionSuccessful). Otherwise they
+     * will be committed.
+     * <p>
+     * Here is the standard idiom for transactions:
+     *
+     * <pre>
+     *   db.beginTransactionNonExclusive();
+     *   try {
+     *     ...
+     *     db.setTransactionSuccessful();
+     *   } finally {
+     *     db.endTransaction();
+     *   }
+     * </pre>
+     */
+    void beginTransactionNonExclusive();
+
+    /**
+     * Begins a transaction in EXCLUSIVE mode.
+     * <p>
+     * Transactions can be nested.
+     * When the outer transaction is ended all of
+     * the work done in that transaction and all of the nested transactions will be committed or
+     * rolled back. The changes will be rolled back if any transaction is ended without being
+     * marked as clean (by calling setTransactionSuccessful). Otherwise they will be committed.
+     * </p>
+     * <p>Here is the standard idiom for transactions:
+     *
+     * <pre>
+     *   db.beginTransactionWithListener(listener);
+     *   try {
+     *     ...
+     *     db.setTransactionSuccessful();
+     *   } finally {
+     *     db.endTransaction();
+     *   }
+     * </pre>
+     *
+     * @param transactionListener listener that should be notified when the transaction begins,
+     *                            commits, or is rolled back, either explicitly or by a call to
+     *                            {@link #yieldIfContendedSafely}.
+     */
+    void beginTransactionWithListener(SQLiteTransactionListener transactionListener);
+
+    /**
+     * Begins a transaction in IMMEDIATE mode. Transactions can be nested. When
+     * the outer transaction is ended all of the work done in that transaction
+     * and all of the nested transactions will be committed or rolled back. The
+     * changes will be rolled back if any transaction is ended without being
+     * marked as clean (by calling setTransactionSuccessful). Otherwise they
+     * will be committed.
+     * <p>
+     * Here is the standard idiom for transactions:
+     *
+     * <pre>
+     *   db.beginTransactionWithListenerNonExclusive(listener);
+     *   try {
+     *     ...
+     *     db.setTransactionSuccessful();
+     *   } finally {
+     *     db.endTransaction();
+     *   }
+     * </pre>
+     *
+     * @param transactionListener listener that should be notified when the
+     *                            transaction begins, commits, or is rolled back, either
+     *                            explicitly or by a call to {@link #yieldIfContendedSafely}.
+     */
+    void beginTransactionWithListenerNonExclusive(SQLiteTransactionListener transactionListener);
+
+    /**
+     * End a transaction. See beginTransaction for notes about how to use this and when transactions
+     * are committed and rolled back.
+     */
+    void endTransaction();
+
+    /**
+     * Marks the current transaction as successful. Do not do any more database work between
+     * calling this and calling endTransaction. Do as little non-database work as possible in that
+     * situation too. If any errors are encountered between this and endTransaction the transaction
+     * will still be committed.
+     *
+     * @throws IllegalStateException if the current thread is not in a transaction or the
+     *                               transaction is already marked as successful.
+     */
+    void setTransactionSuccessful();
+
+    /**
+     * Returns true if the current thread has a transaction pending.
+     *
+     * @return True if the current thread is in a transaction.
+     */
+    boolean inTransaction();
+
+    /**
+     * Returns true if the current thread is holding an active connection to the database.
+     * <p>
+     * The name of this method comes from a time when having an active connection
+     * to the database meant that the thread was holding an actual lock on the
+     * database.  Nowadays, there is no longer a true "database lock" although threads
+     * may block if they cannot acquire a database connection to perform a
+     * particular operation.
+     * </p>
+     *
+     * @return True if the current thread is holding an active connection to the database.
+     */
+    boolean isDbLockedByCurrentThread();
+
+    /**
+     * Temporarily end the transaction to let other threads run. The transaction is assumed to be
+     * successful so far. Do not call setTransactionSuccessful before calling this. When this
+     * returns a new transaction will have been created but not marked as successful. This assumes
+     * that there are no nested transactions (beginTransaction has only been called once) and will
+     * throw an exception if that is not the case.
+     *
+     * @return true if the transaction was yielded
+     */
+    boolean yieldIfContendedSafely();
+
+    /**
+     * Temporarily end the transaction to let other threads run. The transaction is assumed to be
+     * successful so far. Do not call setTransactionSuccessful before calling this. When this
+     * returns a new transaction will have been created but not marked as successful. This assumes
+     * that there are no nested transactions (beginTransaction has only been called once) and will
+     * throw an exception if that is not the case.
+     *
+     * @param sleepAfterYieldDelay if > 0, sleep this long before starting a new transaction if
+     *                             the lock was actually yielded. This will allow other background
+     *                             threads to make some
+     *                             more progress than they would if we started the transaction
+     *                             immediately.
+     * @return true if the transaction was yielded
+     */
+    boolean yieldIfContendedSafely(long sleepAfterYieldDelay);
+
+    /**
+     * Gets the database version.
+     *
+     * @return the database version
+     */
+    int getVersion();
+
+    /**
+     * Sets the database version.
+     *
+     * @param version the new database version
+     */
+    void setVersion(int version);
+
+    /**
+     * Returns the maximum size the database may grow to.
+     *
+     * @return the new maximum database size
+     */
+    long getMaximumSize();
+
+    /**
+     * Sets the maximum size the database will grow to. The maximum size cannot
+     * be set below the current size.
+     *
+     * @param numBytes the maximum database size, in bytes
+     * @return the new maximum database size
+     */
+    long setMaximumSize(long numBytes);
+
+    /**
+     * Returns the current database page size, in bytes.
+     *
+     * @return the database page size, in bytes
+     */
+    long getPageSize();
+
+    /**
+     * Sets the database page size. The page size must be a power of two. This
+     * method does not work if any data has been written to the database file,
+     * and must be called right after the database has been created.
+     *
+     * @param numBytes the database page size, in bytes
+     */
+    void setPageSize(long numBytes);
+
+    /**
+     * Runs the given query on the database. If you would like to have typed bind arguments,
+     * use {@link #query(SupportSQLiteQuery)}.
+     *
+     * @param query The SQL query that includes the query and can bind into a given compiled
+     *              program.
+     * @return A {@link Cursor} object, which is positioned before the first entry. Note that
+     * {@link Cursor}s are not synchronized, see the documentation for more details.
+     * @see #query(SupportSQLiteQuery)
+     */
+    Cursor query(String query);
+
+    /**
+     * Runs the given query on the database. If you would like to have bind arguments,
+     * use {@link #query(SupportSQLiteQuery)}.
+     *
+     * @param query The SQL query that includes the query and can bind into a given compiled
+     *              program.
+     * @param bindArgs The query arguments to bind.
+     * @return A {@link Cursor} object, which is positioned before the first entry. Note that
+     * {@link Cursor}s are not synchronized, see the documentation for more details.
+     * @see #query(SupportSQLiteQuery)
+     */
+    Cursor query(String query, Object[] bindArgs);
+
+    /**
+     * Runs the given query on the database.
+     * <p>
+     * This class allows using type safe sql program bindings while running queries.
+     *
+     * @param query The SQL query that includes the query and can bind into a given compiled
+     *              program.
+     * @return A {@link Cursor} object, which is positioned before the first entry. Note that
+     * {@link Cursor}s are not synchronized, see the documentation for more details.
+     * @see SimpleSQLiteQuery
+     */
+    Cursor query(SupportSQLiteQuery query);
+
+    /**
+     * Runs the given query on the database.
+     * <p>
+     * This class allows using type safe sql program bindings while running queries.
+     *
+     * @param query The SQL query that includes the query and can bind into a given compiled
+     *              program.
+     * @param cancellationSignal A signal to cancel the operation in progress, or null if none.
+     * If the operation is canceled, then {@link OperationCanceledException} will be thrown
+     * when the query is executed.
+     * @return A {@link Cursor} object, which is positioned before the first entry. Note that
+     * {@link Cursor}s are not synchronized, see the documentation for more details.
+     */
+    @RequiresApi(api = Build.VERSION_CODES.JELLY_BEAN)
+    Cursor query(SupportSQLiteQuery query, CancellationSignal cancellationSignal);
+
+    /**
+     * Convenience method for inserting a row into the database.
+     *
+     * @param table          the table to insert the row into
+     * @param values         this map contains the initial column values for the
+     *                       row. The keys should be the column names and the values the
+     *                       column values
+     * @param conflictAlgorithm for insert conflict resolver. One of
+     * {@link SQLiteDatabase#CONFLICT_NONE}, {@link SQLiteDatabase#CONFLICT_ROLLBACK},
+     * {@link SQLiteDatabase#CONFLICT_ABORT}, {@link SQLiteDatabase#CONFLICT_FAIL},
+     * {@link SQLiteDatabase#CONFLICT_IGNORE}, {@link SQLiteDatabase#CONFLICT_REPLACE}.
+     * @return the row ID of the newly inserted row, or -1 if an error occurred
+     * @throws SQLException If the insert fails
+     */
+    long insert(String table, int conflictAlgorithm, ContentValues values) throws SQLException;
+
+    /**
+     * Convenience method for deleting rows in the database.
+     *
+     * @param table       the table to delete from
+     * @param whereClause the optional WHERE clause to apply when deleting.
+     *                    Passing null will delete all rows.
+     * @param whereArgs   You may include ?s in the where clause, which
+     *                    will be replaced by the values from whereArgs. The values
+     *                    will be bound as Strings.
+     * @return the number of rows affected if a whereClause is passed in, 0
+     * otherwise. To remove all rows and get a count pass "1" as the
+     * whereClause.
+     */
+    int delete(String table, String whereClause, Object[] whereArgs);
+
+    /**
+     * Convenience method for updating rows in the database.
+     *
+     * @param table       the table to update in
+     * @param conflictAlgorithm for update conflict resolver. One of
+     * {@link SQLiteDatabase#CONFLICT_NONE}, {@link SQLiteDatabase#CONFLICT_ROLLBACK},
+     * {@link SQLiteDatabase#CONFLICT_ABORT}, {@link SQLiteDatabase#CONFLICT_FAIL},
+     * {@link SQLiteDatabase#CONFLICT_IGNORE}, {@link SQLiteDatabase#CONFLICT_REPLACE}.
+     * @param values      a map from column names to new column values. null is a
+     *                    valid value that will be translated to NULL.
+     * @param whereClause the optional WHERE clause to apply when updating.
+     *                    Passing null will update all rows.
+     * @param whereArgs   You may include ?s in the where clause, which
+     *                    will be replaced by the values from whereArgs. The values
+     *                    will be bound as Strings.
+     * @return the number of rows affected
+     */
+    int update(String table, int conflictAlgorithm,
+            ContentValues values, String whereClause, Object[] whereArgs);
+
+    /**
+     * Execute a single SQL statement that does not return any data.
+     * <p>
+     * When using {@link #enableWriteAheadLogging()}, journal_mode is
+     * automatically managed by this class. So, do not set journal_mode
+     * using "PRAGMA journal_mode'<value>" statement if your app is using
+     * {@link #enableWriteAheadLogging()}
+     * </p>
+     *
+     * @param sql the SQL statement to be executed. Multiple statements separated by semicolons are
+     *            not supported.
+     * @throws SQLException if the SQL string is invalid
+     * @see #query(SupportSQLiteQuery)
+     */
+    void execSQL(String sql) throws SQLException;
+
+    /**
+     * Execute a single SQL statement that does not return any data.
+     * <p>
+     * When using {@link #enableWriteAheadLogging()}, journal_mode is
+     * automatically managed by this class. So, do not set journal_mode
+     * using "PRAGMA journal_mode'<value>" statement if your app is using
+     * {@link #enableWriteAheadLogging()}
+     * </p>
+     *
+     * @param sql      the SQL statement to be executed. Multiple statements separated by semicolons
+     *                 are
+     *                 not supported.
+     * @param bindArgs only byte[], String, Long and Double are supported in selectionArgs.
+     * @throws SQLException if the SQL string is invalid
+     * @see #query(SupportSQLiteQuery)
+     */
+    void execSQL(String sql, Object[] bindArgs) throws SQLException;
+
+    /**
+     * Returns true if the database is opened as read only.
+     *
+     * @return True if database is opened as read only.
+     */
+    boolean isReadOnly();
+
+    /**
+     * Returns true if the database is currently open.
+     *
+     * @return True if the database is currently open (has not been closed).
+     */
+    boolean isOpen();
+
+    /**
+     * Returns true if the new version code is greater than the current database version.
+     *
+     * @param newVersion The new version code.
+     * @return True if the new version code is greater than the current database version.
+     */
+    boolean needUpgrade(int newVersion);
+
+    /**
+     * Gets the path to the database file.
+     *
+     * @return The path to the database file.
+     */
+    String getPath();
+
+    /**
+     * Sets the locale for this database.  Does nothing if this database has
+     * the {@link SQLiteDatabase#NO_LOCALIZED_COLLATORS} flag set or was opened read only.
+     *
+     * @param locale The new locale.
+     * @throws SQLException if the locale could not be set.  The most common reason
+     *                      for this is that there is no collator available for the locale you
+     *                      requested.
+     *                      In this case the database remains unchanged.
+     */
+    void setLocale(Locale locale);
+
+    /**
+     * Sets the maximum size of the prepared-statement cache for this database.
+     * (size of the cache = number of compiled-sql-statements stored in the cache).
+     * <p>
+     * Maximum cache size can ONLY be increased from its current size (default = 10).
+     * If this method is called with smaller size than the current maximum value,
+     * then IllegalStateException is thrown.
+     * <p>
+     * This method is thread-safe.
+     *
+     * @param cacheSize the size of the cache. can be (0 to
+     *                  {@link SQLiteDatabase#MAX_SQL_CACHE_SIZE})
+     * @throws IllegalStateException if input cacheSize gt;
+     *                               {@link SQLiteDatabase#MAX_SQL_CACHE_SIZE}.
+     */
+    void setMaxSqlCacheSize(int cacheSize);
+
+    /**
+     * Sets whether foreign key constraints are enabled for the database.
+     * <p>
+     * By default, foreign key constraints are not enforced by the database.
+     * This method allows an application to enable foreign key constraints.
+     * It must be called each time the database is opened to ensure that foreign
+     * key constraints are enabled for the session.
+     * </p><p>
+     * A good time to call this method is right after calling {@code #openOrCreateDatabase}
+     * or in the {@link SupportSQLiteOpenHelper.Callback#onConfigure} callback.
+     * </p><p>
+     * When foreign key constraints are disabled, the database does not check whether
+     * changes to the database will violate foreign key constraints.  Likewise, when
+     * foreign key constraints are disabled, the database will not execute cascade
+     * delete or update triggers.  As a result, it is possible for the database
+     * state to become inconsistent.  To perform a database integrity check,
+     * call {@link #isDatabaseIntegrityOk}.
+     * </p><p>
+     * This method must not be called while a transaction is in progress.
+     * </p><p>
+     * See also <a href="http://sqlite.org/foreignkeys.html">SQLite Foreign Key Constraints</a>
+     * for more details about foreign key constraint support.
+     * </p>
+     *
+     * @param enable True to enable foreign key constraints, false to disable them.
+     * @throws IllegalStateException if the are transactions is in progress
+     *                               when this method is called.
+     */
+    @RequiresApi(api = Build.VERSION_CODES.JELLY_BEAN)
+    void setForeignKeyConstraintsEnabled(boolean enable);
+
+    /**
+     * This method enables parallel execution of queries from multiple threads on the
+     * same database.  It does this by opening multiple connections to the database
+     * and using a different database connection for each query.  The database
+     * journal mode is also changed to enable writes to proceed concurrently with reads.
+     * <p>
+     * When write-ahead logging is not enabled (the default), it is not possible for
+     * reads and writes to occur on the database at the same time.  Before modifying the
+     * database, the writer implicitly acquires an exclusive lock on the database which
+     * prevents readers from accessing the database until the write is completed.
+     * </p><p>
+     * In contrast, when write-ahead logging is enabled (by calling this method), write
+     * operations occur in a separate log file which allows reads to proceed concurrently.
+     * While a write is in progress, readers on other threads will perceive the state
+     * of the database as it was before the write began.  When the write completes, readers
+     * on other threads will then perceive the new state of the database.
+     * </p><p>
+     * It is a good idea to enable write-ahead logging whenever a database will be
+     * concurrently accessed and modified by multiple threads at the same time.
+     * However, write-ahead logging uses significantly more memory than ordinary
+     * journaling because there are multiple connections to the same database.
+     * So if a database will only be used by a single thread, or if optimizing
+     * concurrency is not very important, then write-ahead logging should be disabled.
+     * </p><p>
+     * After calling this method, execution of queries in parallel is enabled as long as
+     * the database remains open.  To disable execution of queries in parallel, either
+     * call {@link #disableWriteAheadLogging} or close the database and reopen it.
+     * </p><p>
+     * The maximum number of connections used to execute queries in parallel is
+     * dependent upon the device memory and possibly other properties.
+     * </p><p>
+     * If a query is part of a transaction, then it is executed on the same database handle the
+     * transaction was begun.
+     * </p><p>
+     * Writers should use {@link #beginTransactionNonExclusive()} or
+     * {@link #beginTransactionWithListenerNonExclusive(SQLiteTransactionListener)}
+     * to start a transaction.  Non-exclusive mode allows database file to be in readable
+     * by other threads executing queries.
+     * </p><p>
+     * If the database has any attached databases, then execution of queries in parallel is NOT
+     * possible.  Likewise, write-ahead logging is not supported for read-only databases
+     * or memory databases.  In such cases, {@code enableWriteAheadLogging} returns false.
+     * </p><p>
+     * The best way to enable write-ahead logging is to pass the
+     * {@link SQLiteDatabase#ENABLE_WRITE_AHEAD_LOGGING} flag to
+     * {@link SQLiteDatabase#openDatabase}.  This is more efficient than calling
+     * <code><pre>
+     *     SQLiteDatabase db = SQLiteDatabase.openDatabase("db_filename", cursorFactory,
+     *             SQLiteDatabase.CREATE_IF_NECESSARY | SQLiteDatabase.ENABLE_WRITE_AHEAD_LOGGING,
+     *             myDatabaseErrorHandler);
+     *     db.enableWriteAheadLogging();
+     * </pre></code>
+     * </p><p>
+     * Another way to enable write-ahead logging is to call {@code enableWriteAheadLogging}
+     * after opening the database.
+     * <code><pre>
+     *     SQLiteDatabase db = SQLiteDatabase.openDatabase("db_filename", cursorFactory,
+     *             SQLiteDatabase.CREATE_IF_NECESSARY, myDatabaseErrorHandler);
+     *     db.enableWriteAheadLogging();
+     * </pre></code>
+     * </p><p>
+     * See also <a href="http://sqlite.org/wal.html">SQLite Write-Ahead Logging</a> for
+     * more details about how write-ahead logging works.
+     * </p>
+     *
+     * @return True if write-ahead logging is enabled.
+     * @throws IllegalStateException if there are transactions in progress at the
+     *                               time this method is called.  WAL mode can only be changed when
+     *                               there are no
+     *                               transactions in progress.
+     * @see SQLiteDatabase#ENABLE_WRITE_AHEAD_LOGGING
+     * @see #disableWriteAheadLogging
+     */
+    boolean enableWriteAheadLogging();
+
+    /**
+     * This method disables the features enabled by {@link #enableWriteAheadLogging()}.
+     *
+     * @throws IllegalStateException if there are transactions in progress at the
+     *                               time this method is called.  WAL mode can only be changed when
+     *                               there are no
+     *                               transactions in progress.
+     * @see #enableWriteAheadLogging
+     */
+    @RequiresApi(api = Build.VERSION_CODES.JELLY_BEAN)
+    void disableWriteAheadLogging();
+
+    /**
+     * Returns true if write-ahead logging has been enabled for this database.
+     *
+     * @return True if write-ahead logging has been enabled for this database.
+     * @see #enableWriteAheadLogging
+     * @see SQLiteDatabase#ENABLE_WRITE_AHEAD_LOGGING
+     */
+    @RequiresApi(api = Build.VERSION_CODES.JELLY_BEAN)
+    boolean isWriteAheadLoggingEnabled();
+
+    /**
+     * Returns list of full path names of all attached databases including the main database
+     * by executing 'pragma database_list' on the database.
+     *
+     * @return ArrayList of pairs of (database name, database file path) or null if the database
+     * is not open.
+     */
+    List<Pair<String, String>> getAttachedDbs();
+
+    /**
+     * Runs 'pragma integrity_check' on the given database (and all the attached databases)
+     * and returns true if the given database (and all its attached databases) pass integrity_check,
+     * false otherwise.
+     * <p>
+     * If the result is false, then this method logs the errors reported by the integrity_check
+     * command execution.
+     * <p>
+     * Note that 'pragma integrity_check' on a database can take a long time.
+     *
+     * @return true if the given database (and all its attached databases) pass integrity_check,
+     * false otherwise.
+     */
+    boolean isDatabaseIntegrityOk();
+}
diff --git a/room/db/src/main/java/android/arch/persistence/db/SupportSQLiteOpenHelper.java b/room/db/src/main/java/android/arch/persistence/db/SupportSQLiteOpenHelper.java
new file mode 100644
index 0000000..5a96e5a
--- /dev/null
+++ b/room/db/src/main/java/android/arch/persistence/db/SupportSQLiteOpenHelper.java
@@ -0,0 +1,341 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.arch.persistence.db;
+
+import android.content.Context;
+import android.database.DatabaseErrorHandler;
+import android.database.DefaultDatabaseErrorHandler;
+import android.database.sqlite.SQLiteException;
+import android.os.Build;
+import android.support.annotation.NonNull;
+import android.support.annotation.Nullable;
+import android.support.annotation.RequiresApi;
+
+/**
+ * An interface to map the behavior of {@link android.database.sqlite.SQLiteOpenHelper}.
+ * Note that since that class requires overriding certain methods, support implementation
+ * uses {@link Factory#create(Configuration)} to create this and {@link Callback} to implement
+ * the methods that should be overridden.
+ */
+@SuppressWarnings("unused")
+public interface SupportSQLiteOpenHelper {
+    /**
+     * Return the name of the SQLite database being opened, as given to
+     * the constructor.
+     */
+    String getDatabaseName();
+
+    /**
+     * Enables or disables the use of write-ahead logging for the database.
+     *
+     * Write-ahead logging cannot be used with read-only databases so the value of
+     * this flag is ignored if the database is opened read-only.
+     *
+     * @param enabled True if write-ahead logging should be enabled, false if it
+     *                should be disabled.
+     * @see SupportSQLiteDatabase#enableWriteAheadLogging()
+     */
+    @RequiresApi(api = Build.VERSION_CODES.JELLY_BEAN)
+    void setWriteAheadLoggingEnabled(boolean enabled);
+
+    /**
+     * Create and/or open a database that will be used for reading and writing.
+     * The first time this is called, the database will be opened and
+     * {@link Callback#onCreate}, {@link Callback#onUpgrade} and/or {@link Callback#onOpen} will be
+     * called.
+     *
+     * <p>Once opened successfully, the database is cached, so you can
+     * call this method every time you need to write to the database.
+     * (Make sure to call {@link #close} when you no longer need the database.)
+     * Errors such as bad permissions or a full disk may cause this method
+     * to fail, but future attempts may succeed if the problem is fixed.</p>
+     *
+     * <p class="caution">Database upgrade may take a long time, you
+     * should not call this method from the application main thread, including
+     * from {@link android.content.ContentProvider#onCreate ContentProvider.onCreate()}.
+     *
+     * @return a read/write database object valid until {@link #close} is called
+     * @throws SQLiteException if the database cannot be opened for writing
+     */
+    SupportSQLiteDatabase getWritableDatabase();
+
+    /**
+     * Create and/or open a database.  This will be the same object returned by
+     * {@link #getWritableDatabase} unless some problem, such as a full disk,
+     * requires the database to be opened read-only.  In that case, a read-only
+     * database object will be returned.  If the problem is fixed, a future call
+     * to {@link #getWritableDatabase} may succeed, in which case the read-only
+     * database object will be closed and the read/write object will be returned
+     * in the future.
+     *
+     * <p class="caution">Like {@link #getWritableDatabase}, this method may
+     * take a long time to return, so you should not call it from the
+     * application main thread, including from
+     * {@link android.content.ContentProvider#onCreate ContentProvider.onCreate()}.
+     *
+     * @return a database object valid until {@link #getWritableDatabase}
+     * or {@link #close} is called.
+     * @throws SQLiteException if the database cannot be opened
+     */
+    SupportSQLiteDatabase getReadableDatabase();
+
+    /**
+     * Close any open database object.
+     */
+    void close();
+
+    /**
+     * Matching callback methods from {@link android.database.sqlite.SQLiteOpenHelper}.
+     */
+    @SuppressWarnings({"unused", "WeakerAccess"})
+    abstract class Callback {
+        /**
+         * Called when the database connection is being configured, to enable features such as
+         * write-ahead logging or foreign key support.
+         * <p>
+         * This method is called before {@link #onCreate}, {@link #onUpgrade}, {@link #onDowngrade},
+         * or {@link #onOpen} are called. It should not modify the database except to configure the
+         * database connection as required.
+         * </p>
+         * <p>
+         * This method should only call methods that configure the parameters of the database
+         * connection, such as {@link SupportSQLiteDatabase#enableWriteAheadLogging}
+         * {@link SupportSQLiteDatabase#setForeignKeyConstraintsEnabled},
+         * {@link SupportSQLiteDatabase#setLocale},
+         * {@link SupportSQLiteDatabase#setMaximumSize}, or executing PRAGMA statements.
+         * </p>
+         *
+         * @param db The database.
+         */
+        public void onConfigure(SupportSQLiteDatabase db) {
+
+        }
+
+        /**
+         * Called when the database is created for the first time. This is where the
+         * creation of tables and the initial population of the tables should happen.
+         *
+         * @param db The database.
+         */
+        public abstract void onCreate(SupportSQLiteDatabase db);
+
+        /**
+         * Called when the database needs to be upgraded. The implementation
+         * should use this method to drop tables, add tables, or do anything else it
+         * needs to upgrade to the new schema version.
+         *
+         * <p>
+         * The SQLite ALTER TABLE documentation can be found
+         * <a href="http://sqlite.org/lang_altertable.html">here</a>. If you add new columns
+         * you can use ALTER TABLE to insert them into a live table. If you rename or remove columns
+         * you can use ALTER TABLE to rename the old table, then create the new table and then
+         * populate the new table with the contents of the old table.
+         * </p><p>
+         * This method executes within a transaction.  If an exception is thrown, all changes
+         * will automatically be rolled back.
+         * </p>
+         *
+         * @param db         The database.
+         * @param oldVersion The old database version.
+         * @param newVersion The new database version.
+         */
+        public abstract void onUpgrade(SupportSQLiteDatabase db, int oldVersion, int newVersion);
+
+        /**
+         * Called when the database needs to be downgraded. This is strictly similar to
+         * {@link #onUpgrade} method, but is called whenever current version is newer than requested
+         * one.
+         * However, this method is not abstract, so it is not mandatory for a customer to
+         * implement it. If not overridden, default implementation will reject downgrade and
+         * throws SQLiteException
+         *
+         * <p>
+         * This method executes within a transaction.  If an exception is thrown, all changes
+         * will automatically be rolled back.
+         * </p>
+         *
+         * @param db         The database.
+         * @param oldVersion The old database version.
+         * @param newVersion The new database version.
+         */
+        public void onDowngrade(SupportSQLiteDatabase db, int oldVersion, int newVersion) {
+            throw new SQLiteException("Can't downgrade database from version "
+                    + oldVersion + " to " + newVersion);
+        }
+
+        /**
+         * Called when the database has been opened.  The implementation
+         * should check {@link SupportSQLiteDatabase#isReadOnly} before updating the
+         * database.
+         * <p>
+         * This method is called after the database connection has been configured
+         * and after the database schema has been created, upgraded or downgraded as necessary.
+         * If the database connection must be configured in some way before the schema
+         * is created, upgraded, or downgraded, do it in {@link #onConfigure} instead.
+         * </p>
+         *
+         * @param db The database.
+         */
+        public void onOpen(SupportSQLiteDatabase db) {
+
+        }
+    }
+
+    /**
+     * The configuration to create an SQLite open helper object using {@link Factory}.
+     */
+    @SuppressWarnings("WeakerAccess")
+    class Configuration {
+        /**
+         * Context to use to open or create the database.
+         */
+        @NonNull
+        public final Context context;
+        /**
+         * Name of the database file, or null for an in-memory database.
+         */
+        @Nullable
+        public final String name;
+        /**
+         * Version number of the database (starting at 1); if the database is older,
+         * {@link SupportSQLiteOpenHelper.Callback#onUpgrade(SupportSQLiteDatabase, int, int)}
+         * will be used to upgrade the database; if the database is newer,
+         * {@link SupportSQLiteOpenHelper.Callback#onDowngrade(SupportSQLiteDatabase, int, int)}
+         * will be used to downgrade the database.
+         */
+        public final int version;
+        /**
+         * The callback class to handle creation, upgrade and downgrade.
+         */
+        @NonNull
+        public final SupportSQLiteOpenHelper.Callback callback;
+        /**
+         * The {@link DatabaseErrorHandler} to be used when sqlite reports database
+         * corruption, or null to use the default error handler.
+         */
+        @Nullable
+        public final DatabaseErrorHandler errorHandler;
+
+        Configuration(@NonNull Context context, @Nullable String name,
+                int version, @Nullable DatabaseErrorHandler errorHandler,
+                @NonNull Callback callback) {
+            this.context = context;
+            this.name = name;
+            this.version = version;
+            this.callback = callback;
+            this.errorHandler = errorHandler;
+        }
+
+        /**
+         * Creates a new Configuration.Builder to create an instance of Configuration.
+         *
+         * @param context to use to open or create the database.
+         */
+        public static Builder builder(Context context) {
+            return new Builder(context);
+        }
+
+        /**
+         * Builder class for {@link Configuration}.
+         */
+        public static class Builder {
+            Context mContext;
+            String mName;
+            int mVersion = 1;
+            SupportSQLiteOpenHelper.Callback mCallback;
+            DatabaseErrorHandler mErrorHandler;
+
+            public Configuration build() {
+                if (mCallback == null) {
+                    throw new IllegalArgumentException("Must set a callback to create the"
+                            + " configuration.");
+                }
+                if (mContext == null) {
+                    throw new IllegalArgumentException("Must set a non-null context to create"
+                            + " the configuration.");
+                }
+                if (mErrorHandler == null) {
+                    mErrorHandler = new DefaultDatabaseErrorHandler();
+                }
+                return new Configuration(mContext, mName, mVersion, mErrorHandler,
+                        mCallback);
+            }
+
+            Builder(@NonNull Context context) {
+                mContext = context;
+            }
+
+            /**
+             * @param errorHandler The {@link DatabaseErrorHandler} to be used when sqlite
+             *                     reports database corruption, or null to use the default error
+             *                     handler.
+             * @return This
+             */
+            public Builder errorHandler(@Nullable DatabaseErrorHandler errorHandler) {
+                mErrorHandler = errorHandler;
+                return this;
+            }
+
+            /**
+             * @param name Name of the database file, or null for an in-memory database.
+             * @return This
+             */
+            public Builder name(@Nullable String name) {
+                mName = name;
+                return this;
+            }
+
+            /**
+             * @param callback The callback class to handle creation, upgrade and downgrade.
+             * @return this
+             */
+            public Builder callback(@NonNull Callback callback) {
+                mCallback = callback;
+                return this;
+            }
+
+            /**
+             * @param version Version number of the database (starting at 1); if the database is
+             * older,
+             * {@link SupportSQLiteOpenHelper.Callback#onUpgrade(SupportSQLiteDatabase, int, int)}
+             * will be used to upgrade the database; if the database is newer,
+             * {@link SupportSQLiteOpenHelper.Callback#onDowngrade(SupportSQLiteDatabase, int, int)}
+             * will be used to downgrade the database.
+             * @return this
+             */
+            public Builder version(int version) {
+                mVersion = version;
+                return this;
+            }
+        }
+    }
+
+    /**
+     * Factory class to create instances of {@link SupportSQLiteOpenHelper} using
+     * {@link Configuration}.
+     */
+    interface Factory {
+        /**
+         * Creates an instance of {@link SupportSQLiteOpenHelper} using the given configuration.
+         *
+         * @param configuration The configuration to use while creating the open helper.
+         *
+         * @return A SupportSQLiteOpenHelper which can be used to open a database.
+         */
+        SupportSQLiteOpenHelper create(Configuration configuration);
+    }
+}
diff --git a/room/db/src/main/java/android/arch/persistence/db/SupportSQLiteProgram.java b/room/db/src/main/java/android/arch/persistence/db/SupportSQLiteProgram.java
new file mode 100644
index 0000000..aa63821
--- /dev/null
+++ b/room/db/src/main/java/android/arch/persistence/db/SupportSQLiteProgram.java
@@ -0,0 +1,73 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.arch.persistence.db;
+
+/**
+ * An interface to map the behavior of {@link android.database.sqlite.SQLiteProgram}.
+ */
+
+@SuppressWarnings("unused")
+public interface SupportSQLiteProgram {
+    /**
+     * Bind a NULL value to this statement. The value remains bound until
+     * {@link #clearBindings} is called.
+     *
+     * @param index The 1-based index to the parameter to bind null to
+     */
+    void bindNull(int index);
+
+    /**
+     * Bind a long value to this statement. The value remains bound until
+     * {@link #clearBindings} is called.
+     *addToBindArgs
+     * @param index The 1-based index to the parameter to bind
+     * @param value The value to bind
+     */
+    void bindLong(int index, long value);
+
+    /**
+     * Bind a double value to this statement. The value remains bound until
+     * {@link #clearBindings} is called.
+     *
+     * @param index The 1-based index to the parameter to bind
+     * @param value The value to bind
+     */
+    void bindDouble(int index, double value);
+
+    /**
+     * Bind a String value to this statement. The value remains bound until
+     * {@link #clearBindings} is called.
+     *
+     * @param index The 1-based index to the parameter to bind
+     * @param value The value to bind, must not be null
+     */
+    void bindString(int index, String value);
+
+    /**
+     * Bind a byte array value to this statement. The value remains bound until
+     * {@link #clearBindings} is called.
+     *
+     * @param index The 1-based index to the parameter to bind
+     * @param value The value to bind, must not be null
+     */
+    void bindBlob(int index, byte[] value);
+
+    /**
+     * Clears all existing bindings. Unset bindings are treated as NULL.
+     */
+    void clearBindings();
+}
diff --git a/room/db/src/main/java/android/arch/persistence/db/SupportSQLiteQuery.java b/room/db/src/main/java/android/arch/persistence/db/SupportSQLiteQuery.java
new file mode 100644
index 0000000..2007634
--- /dev/null
+++ b/room/db/src/main/java/android/arch/persistence/db/SupportSQLiteQuery.java
@@ -0,0 +1,38 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.arch.persistence.db;
+
+/**
+ * A query with typed bindings. It is better to use this API instead of
+ * {@link android.database.sqlite.SQLiteDatabase#rawQuery(String, String[])} because it allows
+ * binding type safe parameters.
+ */
+public interface SupportSQLiteQuery {
+    /**
+     * The SQL query. This query can have placeholders(?) for bind arguments.
+     *
+     * @return The SQL query to compile
+     */
+    String getSql();
+
+    /**
+     * Callback to bind the query parameters to the compiled statement.
+     *
+     * @param statement The compiled statement
+     */
+    void bindTo(SupportSQLiteProgram statement);
+}
diff --git a/room/db/src/main/java/android/arch/persistence/db/SupportSQLiteQueryBuilder.java b/room/db/src/main/java/android/arch/persistence/db/SupportSQLiteQueryBuilder.java
new file mode 100644
index 0000000..183fb0a
--- /dev/null
+++ b/room/db/src/main/java/android/arch/persistence/db/SupportSQLiteQueryBuilder.java
@@ -0,0 +1,202 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.arch.persistence.db;
+
+import java.util.regex.Pattern;
+
+/**
+ * A simple query builder to create SQL SELECT queries.
+ */
+@SuppressWarnings("unused")
+public class SupportSQLiteQueryBuilder {
+    private static final Pattern sLimitPattern =
+            Pattern.compile("\\s*\\d+\\s*(,\\s*\\d+\\s*)?");
+
+    private boolean mDistinct = false;
+    private final String mTable;
+    private String[] mColumns = null;
+    private String mSelection;
+    private Object[] mBindArgs;
+    private String mGroupBy = null;
+    private String mHaving = null;
+    private String mOrderBy = null;
+    private String mLimit = null;
+
+    /**
+     * Creates a query for the given table name.
+     *
+     * @param tableName The table name(s) to query.
+     *
+     * @return A builder to create a query.
+     */
+    public static SupportSQLiteQueryBuilder builder(String tableName) {
+        return new SupportSQLiteQueryBuilder(tableName);
+    }
+
+    private SupportSQLiteQueryBuilder(String table) {
+        mTable = table;
+    }
+
+    /**
+     * Adds DISTINCT keyword to the query.
+     *
+     * @return this
+     */
+    public SupportSQLiteQueryBuilder distinct() {
+        mDistinct = true;
+        return this;
+    }
+
+    /**
+     * Sets the given list of columns as the columns that will be returned.
+     *
+     * @param columns The list of column names that should be returned.
+     *
+     * @return this
+     */
+    public SupportSQLiteQueryBuilder columns(String[] columns) {
+        mColumns = columns;
+        return this;
+    }
+
+    /**
+     * Sets the arguments for the WHERE clause.
+     *
+     * @param selection The list of selection columns
+     * @param bindArgs The list of bind arguments to match against these columns
+     *
+     * @return this
+     */
+    public SupportSQLiteQueryBuilder selection(String selection, Object[] bindArgs) {
+        mSelection = selection;
+        mBindArgs = bindArgs;
+        return this;
+    }
+
+    /**
+     * Adds a GROUP BY statement.
+     *
+     * @param groupBy The value of the GROUP BY statement.
+     *
+     * @return this
+     */
+    @SuppressWarnings("WeakerAccess")
+    public SupportSQLiteQueryBuilder groupBy(String groupBy) {
+        mGroupBy = groupBy;
+        return this;
+    }
+
+    /**
+     * Adds a HAVING statement. You must also provide {@link #groupBy(String)} for this to work.
+     *
+     * @param having The having clause.
+     *
+     * @return this
+     */
+    public SupportSQLiteQueryBuilder having(String having) {
+        mHaving = having;
+        return this;
+    }
+
+    /**
+     * Adds an ORDER BY statement.
+     *
+     * @param orderBy The order clause.
+     *
+     * @return this
+     */
+    public SupportSQLiteQueryBuilder orderBy(String orderBy) {
+        mOrderBy = orderBy;
+        return this;
+    }
+
+    /**
+     * Adds a LIMIT statement.
+     *
+     * @param limit The limit value.
+     *
+     * @return this
+     */
+    public SupportSQLiteQueryBuilder limit(String limit) {
+        if (!isEmpty(limit) && !sLimitPattern.matcher(limit).matches()) {
+            throw new IllegalArgumentException("invalid LIMIT clauses:" + limit);
+        }
+        mLimit = limit;
+        return this;
+    }
+
+    /**
+     * Creates the {@link SupportSQLiteQuery} that can be passed into
+     * {@link SupportSQLiteDatabase#query(SupportSQLiteQuery)}.
+     *
+     * @return a new query
+     */
+    public SupportSQLiteQuery create() {
+        if (isEmpty(mGroupBy) && !isEmpty(mHaving)) {
+            throw new IllegalArgumentException(
+                    "HAVING clauses are only permitted when using a groupBy clause");
+        }
+        StringBuilder query = new StringBuilder(120);
+
+        query.append("SELECT ");
+        if (mDistinct) {
+            query.append("DISTINCT ");
+        }
+        if (mColumns != null && mColumns.length != 0) {
+            appendColumns(query, mColumns);
+        } else {
+            query.append(" * ");
+        }
+        query.append(" FROM ");
+        query.append(mTable);
+        appendClause(query, " WHERE ", mSelection);
+        appendClause(query, " GROUP BY ", mGroupBy);
+        appendClause(query, " HAVING ", mHaving);
+        appendClause(query, " ORDER BY ", mOrderBy);
+        appendClause(query, " LIMIT ", mLimit);
+
+        return new SimpleSQLiteQuery(query.toString(), mBindArgs);
+    }
+
+    private static void appendClause(StringBuilder s, String name, String clause) {
+        if (!isEmpty(clause)) {
+            s.append(name);
+            s.append(clause);
+        }
+    }
+
+    /**
+     * Add the names that are non-null in columns to s, separating
+     * them with commas.
+     */
+    private static void appendColumns(StringBuilder s, String[] columns) {
+        int n = columns.length;
+
+        for (int i = 0; i < n; i++) {
+            String column = columns[i];
+            if (i > 0) {
+                s.append(", ");
+            }
+            s.append(column);
+        }
+        s.append(' ');
+    }
+
+    private static boolean isEmpty(String input) {
+        return input == null || input.length() == 0;
+    }
+}
diff --git a/room/db/src/main/java/android/arch/persistence/db/SupportSQLiteStatement.java b/room/db/src/main/java/android/arch/persistence/db/SupportSQLiteStatement.java
new file mode 100644
index 0000000..5a329d7
--- /dev/null
+++ b/room/db/src/main/java/android/arch/persistence/db/SupportSQLiteStatement.java
@@ -0,0 +1,72 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.arch.persistence.db;
+
+/**
+ * An interface to map the behavior of {@link android.database.sqlite.SQLiteStatement}.
+ */
+@SuppressWarnings("unused")
+public interface SupportSQLiteStatement extends SupportSQLiteProgram {
+    /**
+     * Execute this SQL statement, if it is not a SELECT / INSERT / DELETE / UPDATE, for example
+     * CREATE / DROP table, view, trigger, index etc.
+     *
+     * @throws android.database.SQLException If the SQL string is invalid for
+     *         some reason
+     */
+    void execute();
+
+    /**
+     * Execute this SQL statement, if the the number of rows affected by execution of this SQL
+     * statement is of any importance to the caller - for example, UPDATE / DELETE SQL statements.
+     *
+     * @return the number of rows affected by this SQL statement execution.
+     * @throws android.database.SQLException If the SQL string is invalid for
+     *         some reason
+     */
+    int executeUpdateDelete();
+
+    /**
+     * Execute this SQL statement and return the ID of the row inserted due to this call.
+     * The SQL statement should be an INSERT for this to be a useful call.
+     *
+     * @return the row ID of the last row inserted, if this insert is successful. -1 otherwise.
+     *
+     * @throws android.database.SQLException If the SQL string is invalid for
+     *         some reason
+     */
+    long executeInsert();
+
+    /**
+     * Execute a statement that returns a 1 by 1 table with a numeric value.
+     * For example, SELECT COUNT(*) FROM table;
+     *
+     * @return The result of the query.
+     *
+     * @throws android.database.sqlite.SQLiteDoneException if the query returns zero rows
+     */
+    long simpleQueryForLong();
+    /**
+     * Execute a statement that returns a 1 by 1 table with a text value.
+     * For example, SELECT COUNT(*) FROM table;
+     *
+     * @return The result of the query.
+     *
+     * @throws android.database.sqlite.SQLiteDoneException if the query returns zero rows
+     */
+    String simpleQueryForString();
+}
diff --git a/room/gradle/wrapper/gradle-wrapper.jar b/room/gradle/wrapper/gradle-wrapper.jar
new file mode 100644
index 0000000..d6e2637
--- /dev/null
+++ b/room/gradle/wrapper/gradle-wrapper.jar
Binary files differ
diff --git a/room/gradle/wrapper/gradle-wrapper.properties b/room/gradle/wrapper/gradle-wrapper.properties
new file mode 100644
index 0000000..a5292b0
--- /dev/null
+++ b/room/gradle/wrapper/gradle-wrapper.properties
@@ -0,0 +1,6 @@
+#Fri Dec 09 13:07:04 PST 2016
+distributionBase=GRADLE_USER_HOME
+distributionPath=wrapper/dists
+zipStoreBase=GRADLE_USER_HOME
+zipStorePath=wrapper/dists
+distributionUrl=https\://services.gradle.org/distributions/gradle-3.2-bin.zip
diff --git a/room/gradlew b/room/gradlew
new file mode 100755
index 0000000..4ef3a87
--- /dev/null
+++ b/room/gradlew
@@ -0,0 +1,171 @@
+#!/usr/bin/env sh
+
+##############################################################################
+##
+##  Gradle start up script for UN*X
+##
+##############################################################################
+
+# Attempt to set APP_HOME
+# Resolve links: $0 may be a link
+PRG="$0"
+# Need this for relative symlinks.
+while [ -h "$PRG" ] ; do
+    ls=`ls -ld "$PRG"`
+    link=`expr "$ls" : '.*-> \(.*\)$'`
+    if expr "$link" : '/.*' > /dev/null; then
+        PRG="$link"
+    else
+        PRG=`dirname "$PRG"`"/$link"
+    fi
+done
+SAVED="`pwd`"
+cd "`dirname \"$PRG\"`/" >/dev/null
+APP_HOME="`pwd -P`"
+cd "$SAVED" >/dev/null
+
+APP_NAME="Gradle"
+APP_BASE_NAME=`basename "$0"`
+
+# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
+DEFAULT_JVM_OPTS=""
+
+# Use the maximum available, or set MAX_FD != -1 to use that value.
+MAX_FD="maximum"
+
+warn ( ) {
+    echo "$*"
+}
+
+die ( ) {
+    echo
+    echo "$*"
+    echo
+    exit 1
+}
+
+# OS specific support (must be 'true' or 'false').
+cygwin=false
+msys=false
+darwin=false
+nonstop=false
+case "`uname`" in
+  CYGWIN* )
+    cygwin=true
+    ;;
+  Darwin* )
+    darwin=true
+    ;;
+  MINGW* )
+    msys=true
+    ;;
+  NONSTOP* )
+    nonstop=true
+    ;;
+esac
+
+CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
+
+# Determine the Java command to use to start the JVM.
+if [ -n "$JAVA_HOME" ] ; then
+    if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
+        # IBM's JDK on AIX uses strange locations for the executables
+        JAVACMD="$JAVA_HOME/jre/sh/java"
+    else
+        JAVACMD="$JAVA_HOME/bin/java"
+    fi
+    if [ ! -x "$JAVACMD" ] ; then
+        die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
+
+Please set the JAVA_HOME variable in your environment to match the
+location of your Java installation."
+    fi
+else
+    JAVACMD="java"
+    which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
+
+Please set the JAVA_HOME variable in your environment to match the
+location of your Java installation."
+fi
+
+# Increase the maximum file descriptors if we can.
+if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then
+    MAX_FD_LIMIT=`ulimit -H -n`
+    if [ $? -eq 0 ] ; then
+        if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then
+            MAX_FD="$MAX_FD_LIMIT"
+        fi
+        ulimit -n $MAX_FD
+        if [ $? -ne 0 ] ; then
+            warn "Could not set maximum file descriptor limit: $MAX_FD"
+        fi
+    else
+        warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT"
+    fi
+fi
+
+# For Darwin, add options to specify how the application appears in the dock
+if $darwin; then
+    GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\""
+fi
+
+# For Cygwin, switch paths to Windows format before running java
+if $cygwin ; then
+    APP_HOME=`cygpath --path --mixed "$APP_HOME"`
+    CLASSPATH=`cygpath --path --mixed "$CLASSPATH"`
+    JAVACMD=`cygpath --unix "$JAVACMD"`
+
+    # We build the pattern for arguments to be converted via cygpath
+    ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null`
+    SEP=""
+    for dir in $ROOTDIRSRAW ; do
+        ROOTDIRS="$ROOTDIRS$SEP$dir"
+        SEP="|"
+    done
+    OURCYGPATTERN="(^($ROOTDIRS))"
+    # Add a user-defined pattern to the cygpath arguments
+    if [ "$GRADLE_CYGPATTERN" != "" ] ; then
+        OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)"
+    fi
+    # Now convert the arguments - kludge to limit ourselves to /bin/sh
+    i=0
+    for arg in "$@" ; do
+        CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -`
+        CHECK2=`echo "$arg"|egrep -c "^-"`                                 ### Determine if an option
+
+        if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then                    ### Added a condition
+            eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"`
+        else
+            eval `echo args$i`="\"$arg\""
+        fi
+        i=$((i+1))
+    done
+    case $i in
+        (0) set -- ;;
+        (1) set -- "$args0" ;;
+        (2) set -- "$args0" "$args1" ;;
+        (3) set -- "$args0" "$args1" "$args2" ;;
+        (4) set -- "$args0" "$args1" "$args2" "$args3" ;;
+        (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;;
+        (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;;
+        (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;;
+        (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;;
+        (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;;
+    esac
+fi
+
+# Escape application args
+for s in "${@}" ; do
+    s=\"$s\"
+    APP_ARGS=$APP_ARGS" "$s
+done
+
+# Collect all arguments for the java command, following the shell quoting and substitution rules
+eval set -- "$DEFAULT_JVM_OPTS" "$JAVA_OPTS" "$GRADLE_OPTS" "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS"
+
+# by default we should be in the correct project dir, but when run from Finder on Mac, the cwd is wrong
+if [ "$(uname)" = "Darwin" ] && [ "$HOME" = "$PWD" ]; then
+  cd "$(dirname "$0")"
+fi
+
+exec "$JAVACMD" "$@"
diff --git a/room/gradlew.bat b/room/gradlew.bat
new file mode 100644
index 0000000..e95643d
--- /dev/null
+++ b/room/gradlew.bat
@@ -0,0 +1,84 @@
+@if "%DEBUG%" == "" @echo off

+@rem ##########################################################################

+@rem

+@rem  Gradle startup script for Windows

+@rem

+@rem ##########################################################################

+

+@rem Set local scope for the variables with windows NT shell

+if "%OS%"=="Windows_NT" setlocal

+

+set DIRNAME=%~dp0

+if "%DIRNAME%" == "" set DIRNAME=.

+set APP_BASE_NAME=%~n0

+set APP_HOME=%DIRNAME%

+

+@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.

+set DEFAULT_JVM_OPTS=

+

+@rem Find java.exe

+if defined JAVA_HOME goto findJavaFromJavaHome

+

+set JAVA_EXE=java.exe

+%JAVA_EXE% -version >NUL 2>&1

+if "%ERRORLEVEL%" == "0" goto init

+

+echo.

+echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.

+echo.

+echo Please set the JAVA_HOME variable in your environment to match the

+echo location of your Java installation.

+

+goto fail

+

+:findJavaFromJavaHome

+set JAVA_HOME=%JAVA_HOME:"=%

+set JAVA_EXE=%JAVA_HOME%/bin/java.exe

+

+if exist "%JAVA_EXE%" goto init

+

+echo.

+echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%

+echo.

+echo Please set the JAVA_HOME variable in your environment to match the

+echo location of your Java installation.

+

+goto fail

+

+:init

+@rem Get command-line arguments, handling Windows variants

+

+if not "%OS%" == "Windows_NT" goto win9xME_args

+

+:win9xME_args

+@rem Slurp the command line arguments.

+set CMD_LINE_ARGS=

+set _SKIP=2

+

+:win9xME_args_slurp

+if "x%~1" == "x" goto execute

+

+set CMD_LINE_ARGS=%*

+

+:execute

+@rem Setup the command line

+

+set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar

+

+@rem Execute Gradle

+"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS%

+

+:end

+@rem End local scope for the variables with windows NT shell

+if "%ERRORLEVEL%"=="0" goto mainEnd

+

+:fail

+rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of

+rem the _cmd.exe /c_ return code!

+if  not "" == "%GRADLE_EXIT_CONSOLE%" exit 1

+exit /b 1

+

+:mainEnd

+if "%OS%"=="Windows_NT" endlocal

+

+:omega

diff --git a/room/integration-tests/testapp/.gitignore b/room/integration-tests/testapp/.gitignore
new file mode 100644
index 0000000..796b96d
--- /dev/null
+++ b/room/integration-tests/testapp/.gitignore
@@ -0,0 +1 @@
+/build
diff --git a/room/integration-tests/testapp/build.gradle b/room/integration-tests/testapp/build.gradle
new file mode 100644
index 0000000..8f0053f
--- /dev/null
+++ b/room/integration-tests/testapp/build.gradle
@@ -0,0 +1,81 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+apply plugin: 'com.android.library'
+
+project.ext.noDocs = true
+
+android {
+    compileSdkVersion tools.current_sdk
+    buildToolsVersion tools.build_tools_version
+
+    defaultConfig {
+        minSdkVersion flatfoot.min_sdk
+        targetSdkVersion tools.current_sdk
+        versionCode 1
+        versionName "1.0"
+
+        testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
+        javaCompileOptions {
+            annotationProcessorOptions {
+                arguments = ["room.schemaLocation": "$projectDir/schemas".toString()]
+            }
+        }
+    }
+    testOptions {
+        unitTests.returnDefaultValues = true
+    }
+    compileOptions {
+        sourceCompatibility JavaVersion.VERSION_1_7
+        targetCompatibility JavaVersion.VERSION_1_7
+    }
+    sourceSets {
+        androidTest.assets.srcDirs += files("$projectDir/schemas".toString())
+    }
+}
+
+dependencies {
+    compile project(":room:common")
+    compile project(":room:db")
+    compile project(":room:db-impl")
+    compile project(':room:runtime')
+    compile project(':arch:runtime')
+
+    compile libs.support.app_compat
+    annotationProcessor project(":room:compiler")
+    androidTestAnnotationProcessor project(":room:compiler")
+
+    androidTestCompile(libs.test_runner) {
+        exclude module: 'support-annotations'
+        exclude module: 'hamcrest-core'
+    }
+    androidTestCompile(libs.espresso_core, {
+        exclude group: 'com.android.support', module: 'support-annotations'
+        exclude module: "hamcrest-core"
+    })
+    // IJ's gradle integration just cannot figure this out ...
+    androidTestCompile project(':lifecycle:extensions')
+    androidTestCompile project(':lifecycle:common')
+    androidTestCompile project(':lifecycle:runtime')
+    androidTestCompile project(':room:testing')
+    androidTestCompile project(':room:rxjava2')
+    androidTestCompile libs.rx_java
+    testCompile libs.mockito_core
+}
+
+createAndroidCheckstyle(project)
+tasks['check'].dependsOn(tasks['connectedCheck'])
+
+uploadArchives.enabled = false
diff --git a/room/integration-tests/testapp/proguard-rules.pro b/room/integration-tests/testapp/proguard-rules.pro
new file mode 100644
index 0000000..b7210d1
--- /dev/null
+++ b/room/integration-tests/testapp/proguard-rules.pro
@@ -0,0 +1,17 @@
+# Add project specific ProGuard rules here.
+# By default, the flags in this file are appended to flags specified
+# in /Users/yboyar/android/sdk/tools/proguard/proguard-android.txt
+# You can edit the include path and order by changing the proguardFiles
+# directive in build.gradle.
+#
+# For more details, see
+#   http://developer.android.com/guide/developing/tools/proguard.html
+
+# Add any project specific keep options here:
+
+# If your project uses WebView with JS, uncomment the following
+# and specify the fully qualified class name to the JavaScript interface
+# class:
+#-keepclassmembers class fqcn.of.javascript.interface.for.webview {
+#   public *;
+#}
diff --git a/room/integration-tests/testapp/schemas/android.arch.persistence.room.integration.testapp.migration.MigrationDb/1.json b/room/integration-tests/testapp/schemas/android.arch.persistence.room.integration.testapp.migration.MigrationDb/1.json
new file mode 100644
index 0000000..fba47c4
--- /dev/null
+++ b/room/integration-tests/testapp/schemas/android.arch.persistence.room.integration.testapp.migration.MigrationDb/1.json
@@ -0,0 +1,36 @@
+{
+  "formatVersion": 1,
+  "database": {
+    "version": 1,
+    "identityHash": "2f3557e56d7f665363f3e20d14787a59",
+    "entities": [
+      {
+        "tableName": "Entity1",
+        "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER, `name` TEXT, PRIMARY KEY(`id`))",
+        "fields": [
+          {
+            "fieldPath": "id",
+            "columnName": "id",
+            "affinity": "INTEGER"
+          },
+          {
+            "fieldPath": "name",
+            "columnName": "name",
+            "affinity": "TEXT"
+          }
+        ],
+        "primaryKey": {
+          "columnNames": [
+            "id"
+          ],
+          "autoGenerate": false
+        },
+        "indices": []
+      }
+    ],
+    "setupQueries": [
+      "CREATE TABLE IF NOT EXISTS room_master_table (id INTEGER PRIMARY KEY,identity_hash TEXT)",
+      "INSERT OR REPLACE INTO room_master_table (id,identity_hash) VALUES(42, \"2f3557e56d7f665363f3e20d14787a59\")"
+    ]
+  }
+}
\ No newline at end of file
diff --git a/room/integration-tests/testapp/schemas/android.arch.persistence.room.integration.testapp.migration.MigrationDb/2.json b/room/integration-tests/testapp/schemas/android.arch.persistence.room.integration.testapp.migration.MigrationDb/2.json
new file mode 100644
index 0000000..db6af46
--- /dev/null
+++ b/room/integration-tests/testapp/schemas/android.arch.persistence.room.integration.testapp.migration.MigrationDb/2.json
@@ -0,0 +1,59 @@
+{
+  "formatVersion": 1,
+  "database": {
+    "version": 2,
+    "identityHash": "aee9a6eed720c059df0f2ee0d6e96d89",
+    "entities": [
+      {
+        "tableName": "Entity1",
+        "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER, `name` TEXT, PRIMARY KEY(`id`))",
+        "fields": [
+          {
+            "fieldPath": "id",
+            "columnName": "id",
+            "affinity": "INTEGER"
+          },
+          {
+            "fieldPath": "name",
+            "columnName": "name",
+            "affinity": "TEXT"
+          }
+        ],
+        "primaryKey": {
+          "columnNames": [
+            "id"
+          ],
+          "autoGenerate": false
+        },
+        "indices": []
+      },
+      {
+        "tableName": "Entity2",
+        "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER, `name` TEXT, PRIMARY KEY(`id`))",
+        "fields": [
+          {
+            "fieldPath": "id",
+            "columnName": "id",
+            "affinity": "INTEGER"
+          },
+          {
+            "fieldPath": "name",
+            "columnName": "name",
+            "affinity": "TEXT"
+          }
+        ],
+        "primaryKey": {
+          "columnNames": [
+            "id"
+          ],
+          "autoGenerate": false
+        },
+        "indices": []
+      }
+    ],
+    "setupQueries": [
+      "CREATE TABLE IF NOT EXISTS room_master_table (id INTEGER PRIMARY KEY,identity_hash TEXT)",
+      "INSERT OR REPLACE INTO room_master_table (id,identity_hash) VALUES(42, \"aee9a6eed720c059df0f2ee0d6e96d89\")"
+    ]
+  }
+}
\ No newline at end of file
diff --git a/room/integration-tests/testapp/schemas/android.arch.persistence.room.integration.testapp.migration.MigrationDb/3.json b/room/integration-tests/testapp/schemas/android.arch.persistence.room.integration.testapp.migration.MigrationDb/3.json
new file mode 100644
index 0000000..4d9dcb3
--- /dev/null
+++ b/room/integration-tests/testapp/schemas/android.arch.persistence.room.integration.testapp.migration.MigrationDb/3.json
@@ -0,0 +1,64 @@
+{
+  "formatVersion": 1,
+  "database": {
+    "version": 3,
+    "identityHash": "3f2a99b6d768af0184e077808f7348fe",
+    "entities": [
+      {
+        "tableName": "Entity1",
+        "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER, `name` TEXT, PRIMARY KEY(`id`))",
+        "fields": [
+          {
+            "fieldPath": "id",
+            "columnName": "id",
+            "affinity": "INTEGER"
+          },
+          {
+            "fieldPath": "name",
+            "columnName": "name",
+            "affinity": "TEXT"
+          }
+        ],
+        "primaryKey": {
+          "columnNames": [
+            "id"
+          ],
+          "autoGenerate": false
+        },
+        "indices": []
+      },
+      {
+        "tableName": "Entity2",
+        "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER, `addedInV3` TEXT, `name` TEXT, PRIMARY KEY(`id`))",
+        "fields": [
+          {
+            "fieldPath": "id",
+            "columnName": "id",
+            "affinity": "INTEGER"
+          },
+          {
+            "fieldPath": "addedInV3",
+            "columnName": "addedInV3",
+            "affinity": "TEXT"
+          },
+          {
+            "fieldPath": "name",
+            "columnName": "name",
+            "affinity": "TEXT"
+          }
+        ],
+        "primaryKey": {
+          "columnNames": [
+            "id"
+          ],
+          "autoGenerate": false
+        },
+        "indices": []
+      }
+    ],
+    "setupQueries": [
+      "CREATE TABLE IF NOT EXISTS room_master_table (id INTEGER PRIMARY KEY,identity_hash TEXT)",
+      "INSERT OR REPLACE INTO room_master_table (id,identity_hash) VALUES(42, \"3f2a99b6d768af0184e077808f7348fe\")"
+    ]
+  }
+}
\ No newline at end of file
diff --git a/room/integration-tests/testapp/schemas/android.arch.persistence.room.integration.testapp.migration.MigrationDb/4.json b/room/integration-tests/testapp/schemas/android.arch.persistence.room.integration.testapp.migration.MigrationDb/4.json
new file mode 100644
index 0000000..7bc6842
--- /dev/null
+++ b/room/integration-tests/testapp/schemas/android.arch.persistence.room.integration.testapp.migration.MigrationDb/4.json
@@ -0,0 +1,92 @@
+{
+  "formatVersion": 1,
+  "database": {
+    "version": 4,
+    "identityHash": "abbae5f17d94ff7c2c7e05ca217ccc31",
+    "entities": [
+      {
+        "tableName": "Entity1",
+        "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER, `name` TEXT, PRIMARY KEY(`id`))",
+        "fields": [
+          {
+            "fieldPath": "id",
+            "columnName": "id",
+            "affinity": "INTEGER"
+          },
+          {
+            "fieldPath": "name",
+            "columnName": "name",
+            "affinity": "TEXT"
+          }
+        ],
+        "primaryKey": {
+          "columnNames": [
+            "id"
+          ],
+          "autoGenerate": false
+        },
+        "indices": []
+      },
+      {
+        "tableName": "Entity2",
+        "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER, `addedInV3` TEXT, `name` TEXT, PRIMARY KEY(`id`))",
+        "fields": [
+          {
+            "fieldPath": "id",
+            "columnName": "id",
+            "affinity": "INTEGER"
+          },
+          {
+            "fieldPath": "addedInV3",
+            "columnName": "addedInV3",
+            "affinity": "TEXT"
+          },
+          {
+            "fieldPath": "name",
+            "columnName": "name",
+            "affinity": "TEXT"
+          }
+        ],
+        "primaryKey": {
+          "columnNames": [
+            "id"
+          ],
+          "autoGenerate": false
+        },
+        "indices": []
+      },
+      {
+        "tableName": "Entity3",
+        "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER, `removedInV5` TEXT, `name` TEXT, PRIMARY KEY(`id`))",
+        "fields": [
+          {
+            "fieldPath": "id",
+            "columnName": "id",
+            "affinity": "INTEGER"
+          },
+          {
+            "fieldPath": "removedInV5",
+            "columnName": "removedInV5",
+            "affinity": "TEXT"
+          },
+          {
+            "fieldPath": "name",
+            "columnName": "name",
+            "affinity": "TEXT"
+          }
+        ],
+        "primaryKey": {
+          "columnNames": [
+            "id"
+          ],
+          "autoGenerate": false
+        },
+        "indices": []
+      }
+    ],
+    "setupQueries": [
+      "CREATE TABLE IF NOT EXISTS room_master_table (id INTEGER PRIMARY KEY,identity_hash TEXT)",
+      "INSERT OR REPLACE INTO room_master_table (id,identity_hash) VALUES(42, \"abbae5f17d94ff7c2c7e05ca217ccc31\")"
+    ]
+  }
+}
\ No newline at end of file
diff --git a/room/integration-tests/testapp/schemas/android.arch.persistence.room.integration.testapp.migration.MigrationDb/5.json b/room/integration-tests/testapp/schemas/android.arch.persistence.room.integration.testapp.migration.MigrationDb/5.json
new file mode 100644
index 0000000..c7d2dd1
--- /dev/null
+++ b/room/integration-tests/testapp/schemas/android.arch.persistence.room.integration.testapp.migration.MigrationDb/5.json
@@ -0,0 +1,87 @@
+{
+  "formatVersion": 1,
+  "database": {
+    "version": 5,
+    "identityHash": "5543c44fe679f4cf8f03093d66838068",
+    "entities": [
+      {
+        "tableName": "Entity1",
+        "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER, `name` TEXT, PRIMARY KEY(`id`))",
+        "fields": [
+          {
+            "fieldPath": "id",
+            "columnName": "id",
+            "affinity": "INTEGER"
+          },
+          {
+            "fieldPath": "name",
+            "columnName": "name",
+            "affinity": "TEXT"
+          }
+        ],
+        "primaryKey": {
+          "columnNames": [
+            "id"
+          ],
+          "autoGenerate": false
+        },
+        "indices": []
+      },
+      {
+        "tableName": "Entity2",
+        "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER, `addedInV3` TEXT, `name` TEXT, PRIMARY KEY(`id`))",
+        "fields": [
+          {
+            "fieldPath": "id",
+            "columnName": "id",
+            "affinity": "INTEGER"
+          },
+          {
+            "fieldPath": "addedInV3",
+            "columnName": "addedInV3",
+            "affinity": "TEXT"
+          },
+          {
+            "fieldPath": "name",
+            "columnName": "name",
+            "affinity": "TEXT"
+          }
+        ],
+        "primaryKey": {
+          "columnNames": [
+            "id"
+          ],
+          "autoGenerate": false
+        },
+        "indices": []
+      },
+      {
+        "tableName": "Entity3",
+        "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER, `name` TEXT, PRIMARY KEY(`id`))",
+        "fields": [
+          {
+            "fieldPath": "id",
+            "columnName": "id",
+            "affinity": "INTEGER"
+          },
+          {
+            "fieldPath": "name",
+            "columnName": "name",
+            "affinity": "TEXT"
+          }
+        ],
+        "primaryKey": {
+          "columnNames": [
+            "id"
+          ],
+          "autoGenerate": false
+        },
+        "indices": []
+      }
+    ],
+    "setupQueries": [
+      "CREATE TABLE IF NOT EXISTS room_master_table (id INTEGER PRIMARY KEY,identity_hash TEXT)",
+      "INSERT OR REPLACE INTO room_master_table (id,identity_hash) VALUES(42, \"5543c44fe679f4cf8f03093d66838068\")"
+    ]
+  }
+}
\ No newline at end of file
diff --git a/room/integration-tests/testapp/schemas/android.arch.persistence.room.integration.testapp.migration.MigrationDb/6.json b/room/integration-tests/testapp/schemas/android.arch.persistence.room.integration.testapp.migration.MigrationDb/6.json
new file mode 100644
index 0000000..a31ad21
--- /dev/null
+++ b/room/integration-tests/testapp/schemas/android.arch.persistence.room.integration.testapp.migration.MigrationDb/6.json
@@ -0,0 +1,64 @@
+{
+  "formatVersion": 1,
+  "database": {
+    "version": 6,
+    "identityHash": "3f2a99b6d768af0184e077808f7348fe",
+    "entities": [
+      {
+        "tableName": "Entity1",
+        "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER, `name` TEXT, PRIMARY KEY(`id`))",
+        "fields": [
+          {
+            "fieldPath": "id",
+            "columnName": "id",
+            "affinity": "INTEGER"
+          },
+          {
+            "fieldPath": "name",
+            "columnName": "name",
+            "affinity": "TEXT"
+          }
+        ],
+        "primaryKey": {
+          "columnNames": [
+            "id"
+          ],
+          "autoGenerate": false
+        },
+        "indices": []
+      },
+      {
+        "tableName": "Entity2",
+        "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER, `addedInV3` TEXT, `name` TEXT, PRIMARY KEY(`id`))",
+        "fields": [
+          {
+            "fieldPath": "id",
+            "columnName": "id",
+            "affinity": "INTEGER"
+          },
+          {
+            "fieldPath": "addedInV3",
+            "columnName": "addedInV3",
+            "affinity": "TEXT"
+          },
+          {
+            "fieldPath": "name",
+            "columnName": "name",
+            "affinity": "TEXT"
+          }
+        ],
+        "primaryKey": {
+          "columnNames": [
+            "id"
+          ],
+          "autoGenerate": false
+        },
+        "indices": []
+      }
+    ],
+    "setupQueries": [
+      "CREATE TABLE IF NOT EXISTS room_master_table (id INTEGER PRIMARY KEY,identity_hash TEXT)",
+      "INSERT OR REPLACE INTO room_master_table (id,identity_hash) VALUES(42, \"3f2a99b6d768af0184e077808f7348fe\")"
+    ]
+  }
+}
\ No newline at end of file
diff --git a/room/integration-tests/testapp/schemas/android.arch.persistence.room.integration.testapp.migration.MigrationDb/7.json b/room/integration-tests/testapp/schemas/android.arch.persistence.room.integration.testapp.migration.MigrationDb/7.json
new file mode 100644
index 0000000..33a7d1f
--- /dev/null
+++ b/room/integration-tests/testapp/schemas/android.arch.persistence.room.integration.testapp.migration.MigrationDb/7.json
@@ -0,0 +1,111 @@
+{
+  "formatVersion": 1,
+  "database": {
+    "version": 7,
+    "identityHash": "885b872dd8718be5726ae37479ad74e0",
+    "entities": [
+      {
+        "tableName": "Entity1",
+        "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER, `name` TEXT, PRIMARY KEY(`id`))",
+        "fields": [
+          {
+            "fieldPath": "id",
+            "columnName": "id",
+            "affinity": "INTEGER"
+          },
+          {
+            "fieldPath": "name",
+            "columnName": "name",
+            "affinity": "TEXT"
+          }
+        ],
+        "primaryKey": {
+          "columnNames": [
+            "id"
+          ],
+          "autoGenerate": false
+        },
+        "indices": [
+          {
+            "name": "index_Entity1_name",
+            "unique": true,
+            "columnNames": [
+              "name"
+            ],
+            "createSql": "CREATE UNIQUE INDEX `index_Entity1_name` ON `${TABLE_NAME}` (`name`)"
+          }
+        ],
+        "foreignKeys": []
+      },
+      {
+        "tableName": "Entity2",
+        "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER, `addedInV3` TEXT, `name` TEXT, PRIMARY KEY(`id`))",
+        "fields": [
+          {
+            "fieldPath": "id",
+            "columnName": "id",
+            "affinity": "INTEGER"
+          },
+          {
+            "fieldPath": "addedInV3",
+            "columnName": "addedInV3",
+            "affinity": "TEXT"
+          },
+          {
+            "fieldPath": "name",
+            "columnName": "name",
+            "affinity": "TEXT"
+          }
+        ],
+        "primaryKey": {
+          "columnNames": [
+            "id"
+          ],
+          "autoGenerate": false
+        },
+        "indices": [],
+        "foreignKeys": []
+      },
+      {
+        "tableName": "Entity4",
+        "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER, `name` TEXT, PRIMARY KEY(`id`), FOREIGN KEY(`name`) REFERENCES `Entity1`(`name`) ON UPDATE NO ACTION ON DELETE NO ACTION DEFERRABLE INITIALLY DEFERRED)",
+        "fields": [
+          {
+            "fieldPath": "id",
+            "columnName": "id",
+            "affinity": "INTEGER"
+          },
+          {
+            "fieldPath": "name",
+            "columnName": "name",
+            "affinity": "TEXT"
+          }
+        ],
+        "primaryKey": {
+          "columnNames": [
+            "id"
+          ],
+          "autoGenerate": false
+        },
+        "indices": [],
+        "foreignKeys": [
+          {
+            "table": "Entity1",
+            "onDelete": "NO ACTION",
+            "onUpdate": "NO ACTION",
+            "columns": [
+              "name"
+            ],
+            "referencedColumns": [
+              "name"
+            ]
+          }
+        ]
+      }
+    ],
+    "setupQueries": [
+      "CREATE TABLE IF NOT EXISTS room_master_table (id INTEGER PRIMARY KEY,identity_hash TEXT)",
+      "INSERT OR REPLACE INTO room_master_table (id,identity_hash) VALUES(42, \"885b872dd8718be5726ae37479ad74e0\")"
+    ]
+  }
+}
\ No newline at end of file
diff --git a/room/integration-tests/testapp/src/androidTest/java/android/arch/persistence/room/InvalidationTrackerTrojan.java b/room/integration-tests/testapp/src/androidTest/java/android/arch/persistence/room/InvalidationTrackerTrojan.java
new file mode 100644
index 0000000..5fa15ac
--- /dev/null
+++ b/room/integration-tests/testapp/src/androidTest/java/android/arch/persistence/room/InvalidationTrackerTrojan.java
@@ -0,0 +1,26 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.arch.persistence.room;
+
+/**
+ * Trojan class to be able to assert internal state.
+ */
+public class InvalidationTrackerTrojan {
+    public static int countObservers(InvalidationTracker tracker) {
+        return tracker.mObserverMap.size();
+    }
+}
diff --git a/room/integration-tests/testapp/src/androidTest/java/android/arch/persistence/room/integration/testapp/PKeyTestDatabase.java b/room/integration-tests/testapp/src/androidTest/java/android/arch/persistence/room/integration/testapp/PKeyTestDatabase.java
new file mode 100644
index 0000000..e61d808
--- /dev/null
+++ b/room/integration-tests/testapp/src/androidTest/java/android/arch/persistence/room/integration/testapp/PKeyTestDatabase.java
@@ -0,0 +1,68 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.arch.persistence.room.integration.testapp;
+
+import android.arch.persistence.room.Dao;
+import android.arch.persistence.room.Database;
+import android.arch.persistence.room.Insert;
+import android.arch.persistence.room.Query;
+import android.arch.persistence.room.RoomDatabase;
+import android.arch.persistence.room.integration.testapp.vo.IntAutoIncPKeyEntity;
+import android.arch.persistence.room.integration.testapp.vo.IntegerAutoIncPKeyEntity;
+
+import java.util.List;
+
+@Database(entities = {IntAutoIncPKeyEntity.class, IntegerAutoIncPKeyEntity.class}, version = 1,
+        exportSchema = false)
+public abstract class PKeyTestDatabase extends RoomDatabase {
+    public abstract IntPKeyDao intPKeyDao();
+    public abstract IntegerPKeyDao integerPKeyDao();
+
+    @Dao
+    public interface IntPKeyDao {
+        @Insert
+        void insertMe(IntAutoIncPKeyEntity... items);
+        @Insert
+        long insertAndGetId(IntAutoIncPKeyEntity item);
+
+        @Insert
+        long[] insertAndGetIds(IntAutoIncPKeyEntity... item);
+
+        @Query("select * from IntAutoIncPKeyEntity WHERE pKey = :key")
+        IntAutoIncPKeyEntity getMe(int key);
+
+        @Query("select data from IntAutoIncPKeyEntity WHERE pKey IN(:ids)")
+        List<String> loadDataById(long... ids);
+    }
+
+    @Dao
+    public interface IntegerPKeyDao {
+        @Insert
+        void insertMe(IntegerAutoIncPKeyEntity items);
+        @Query("select * from IntegerAutoIncPKeyEntity WHERE pKey = :key")
+        IntegerAutoIncPKeyEntity getMe(int key);
+
+        @Insert
+        long insertAndGetId(IntegerAutoIncPKeyEntity item);
+
+        @Insert
+        long[] insertAndGetIds(IntegerAutoIncPKeyEntity... item);
+
+        @Query("select data from IntegerAutoIncPKeyEntity WHERE pKey IN(:ids)")
+        List<String> loadDataById(long... ids);
+    }
+}
diff --git a/room/integration-tests/testapp/src/androidTest/java/android/arch/persistence/room/integration/testapp/TestDatabase.java b/room/integration-tests/testapp/src/androidTest/java/android/arch/persistence/room/integration/testapp/TestDatabase.java
new file mode 100644
index 0000000..5c9bcc7
--- /dev/null
+++ b/room/integration-tests/testapp/src/androidTest/java/android/arch/persistence/room/integration/testapp/TestDatabase.java
@@ -0,0 +1,68 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.arch.persistence.room.integration.testapp;
+
+import android.arch.persistence.room.Database;
+import android.arch.persistence.room.RoomDatabase;
+import android.arch.persistence.room.TypeConverter;
+import android.arch.persistence.room.TypeConverters;
+import android.arch.persistence.room.integration.testapp.dao.BlobEntityDao;
+import android.arch.persistence.room.integration.testapp.dao.PetCoupleDao;
+import android.arch.persistence.room.integration.testapp.dao.PetDao;
+import android.arch.persistence.room.integration.testapp.dao.SchoolDao;
+import android.arch.persistence.room.integration.testapp.dao.ToyDao;
+import android.arch.persistence.room.integration.testapp.dao.UserDao;
+import android.arch.persistence.room.integration.testapp.dao.UserPetDao;
+import android.arch.persistence.room.integration.testapp.vo.BlobEntity;
+import android.arch.persistence.room.integration.testapp.vo.Pet;
+import android.arch.persistence.room.integration.testapp.vo.PetCouple;
+import android.arch.persistence.room.integration.testapp.vo.School;
+import android.arch.persistence.room.integration.testapp.vo.Toy;
+import android.arch.persistence.room.integration.testapp.vo.User;
+
+import java.util.Date;
+
+@Database(entities = {User.class, Pet.class, School.class, PetCouple.class, Toy.class,
+        BlobEntity.class},
+        version = 1, exportSchema = false)
+@TypeConverters(TestDatabase.Converters.class)
+public abstract class TestDatabase extends RoomDatabase {
+    public abstract UserDao getUserDao();
+    public abstract PetDao getPetDao();
+    public abstract UserPetDao getUserPetDao();
+    public abstract SchoolDao getSchoolDao();
+    public abstract PetCoupleDao getPetCoupleDao();
+    public abstract ToyDao getToyDao();
+    public abstract BlobEntityDao getBlobEntityDao();
+
+    @SuppressWarnings("unused")
+    public static class Converters {
+        @TypeConverter
+        public Date fromTimestamp(Long value) {
+            return value == null ? null : new Date(value);
+        }
+
+        @TypeConverter
+        public Long dateToTimestamp(Date date) {
+            if (date == null) {
+                return null;
+            } else {
+                return date.getTime();
+            }
+        }
+    }
+}
diff --git a/room/integration-tests/testapp/src/androidTest/java/android/arch/persistence/room/integration/testapp/dao/BlobEntityDao.java b/room/integration-tests/testapp/src/androidTest/java/android/arch/persistence/room/integration/testapp/dao/BlobEntityDao.java
new file mode 100644
index 0000000..212eba7
--- /dev/null
+++ b/room/integration-tests/testapp/src/androidTest/java/android/arch/persistence/room/integration/testapp/dao/BlobEntityDao.java
@@ -0,0 +1,40 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.arch.persistence.room.integration.testapp.dao;
+
+import android.arch.persistence.room.Dao;
+import android.arch.persistence.room.Insert;
+import android.arch.persistence.room.Query;
+import android.arch.persistence.room.integration.testapp.vo.BlobEntity;
+
+import java.util.List;
+
+@Dao
+public interface BlobEntityDao {
+
+    @Insert
+    void insert(BlobEntity item);
+
+    @Query("SELECT * FROM BlobEntity")
+    List<BlobEntity> selectAll();
+
+    @Query("SELECT content FROM BlobEntity WHERE id = :id")
+    byte[] getContent(long id);
+
+    @Query("UPDATE BlobEntity SET content = :content WHERE id = :id")
+    void updateContent(long id, byte[] content);
+}
diff --git a/room/integration-tests/testapp/src/androidTest/java/android/arch/persistence/room/integration/testapp/dao/PetCoupleDao.java b/room/integration-tests/testapp/src/androidTest/java/android/arch/persistence/room/integration/testapp/dao/PetCoupleDao.java
new file mode 100644
index 0000000..4f7c4e2
--- /dev/null
+++ b/room/integration-tests/testapp/src/androidTest/java/android/arch/persistence/room/integration/testapp/dao/PetCoupleDao.java
@@ -0,0 +1,33 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.arch.persistence.room.integration.testapp.dao;
+
+import android.arch.persistence.room.Dao;
+import android.arch.persistence.room.Insert;
+import android.arch.persistence.room.Query;
+import android.arch.persistence.room.integration.testapp.vo.PetCouple;
+
+import java.util.List;
+
+@Dao
+public interface PetCoupleDao {
+    @Insert
+    void insert(PetCouple couple);
+
+    @Query("SELECT * FROM PetCouple")
+    List<PetCouple> loadAll();
+}
diff --git a/room/integration-tests/testapp/src/androidTest/java/android/arch/persistence/room/integration/testapp/dao/PetDao.java b/room/integration-tests/testapp/src/androidTest/java/android/arch/persistence/room/integration/testapp/dao/PetDao.java
new file mode 100644
index 0000000..5179655
--- /dev/null
+++ b/room/integration-tests/testapp/src/androidTest/java/android/arch/persistence/room/integration/testapp/dao/PetDao.java
@@ -0,0 +1,35 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.arch.persistence.room.integration.testapp.dao;
+
+import android.arch.persistence.room.Dao;
+import android.arch.persistence.room.Insert;
+import android.arch.persistence.room.OnConflictStrategy;
+import android.arch.persistence.room.Query;
+import android.arch.persistence.room.integration.testapp.vo.Pet;
+
+@Dao
+public interface PetDao {
+    @Insert(onConflict = OnConflictStrategy.REPLACE)
+    void insertOrReplace(Pet... pets);
+
+    @Insert
+    void insertAll(Pet[] pets);
+
+    @Query("SELECT COUNT(*) FROM Pet")
+    int count();
+}
diff --git a/room/integration-tests/testapp/src/androidTest/java/android/arch/persistence/room/integration/testapp/dao/SchoolDao.java b/room/integration-tests/testapp/src/androidTest/java/android/arch/persistence/room/integration/testapp/dao/SchoolDao.java
new file mode 100644
index 0000000..7bb137f
--- /dev/null
+++ b/room/integration-tests/testapp/src/androidTest/java/android/arch/persistence/room/integration/testapp/dao/SchoolDao.java
@@ -0,0 +1,50 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.arch.persistence.room.integration.testapp.dao;
+
+import android.arch.persistence.room.Dao;
+import android.arch.persistence.room.Insert;
+import android.arch.persistence.room.OnConflictStrategy;
+import android.arch.persistence.room.Query;
+import android.arch.persistence.room.RoomWarnings;
+import android.arch.persistence.room.integration.testapp.vo.Coordinates;
+import android.arch.persistence.room.integration.testapp.vo.School;
+import android.arch.persistence.room.integration.testapp.vo.SchoolRef;
+
+import java.util.List;
+
+@Dao
+public abstract class SchoolDao {
+    @Insert(onConflict = OnConflictStrategy.REPLACE)
+    public abstract void insert(School... schools);
+
+    @Query("SELECT * from School WHERE address_street LIKE '%' || :street || '%'")
+    public abstract List<School> findByStreet(String street);
+
+    @Query("SELECT mName, manager_mName FROM School")
+    public abstract List<School> schoolAndManagerNames();
+
+    @Query("SELECT mName, manager_mName FROM School")
+    @SuppressWarnings(RoomWarnings.CURSOR_MISMATCH)
+    public abstract List<SchoolRef> schoolAndManagerNamesAsPojo();
+
+    @Query("SELECT address_lat as lat, address_lng as lng FROM School WHERE mId = :schoolId")
+    public abstract Coordinates loadCoordinates(int schoolId);
+
+    @Query("SELECT address_lat, address_lng FROM School WHERE mId = :schoolId")
+    public abstract School loadCoordinatesAsSchool(int schoolId);
+}
diff --git a/room/integration-tests/testapp/src/androidTest/java/android/arch/persistence/room/integration/testapp/dao/ToyDao.java b/room/integration-tests/testapp/src/androidTest/java/android/arch/persistence/room/integration/testapp/dao/ToyDao.java
new file mode 100644
index 0000000..5d50a62
--- /dev/null
+++ b/room/integration-tests/testapp/src/androidTest/java/android/arch/persistence/room/integration/testapp/dao/ToyDao.java
@@ -0,0 +1,27 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.arch.persistence.room.integration.testapp.dao;
+
+import android.arch.persistence.room.Dao;
+import android.arch.persistence.room.Insert;
+import android.arch.persistence.room.integration.testapp.vo.Toy;
+
+@Dao
+public interface ToyDao {
+    @Insert
+    void insert(Toy... toys);
+}
diff --git a/room/integration-tests/testapp/src/androidTest/java/android/arch/persistence/room/integration/testapp/dao/UserDao.java b/room/integration-tests/testapp/src/androidTest/java/android/arch/persistence/room/integration/testapp/dao/UserDao.java
new file mode 100644
index 0000000..3360893
--- /dev/null
+++ b/room/integration-tests/testapp/src/androidTest/java/android/arch/persistence/room/integration/testapp/dao/UserDao.java
@@ -0,0 +1,159 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.arch.persistence.room.integration.testapp.dao;
+
+import android.arch.lifecycle.LiveData;
+import android.arch.persistence.room.Dao;
+import android.arch.persistence.room.Delete;
+import android.arch.persistence.room.Insert;
+import android.arch.persistence.room.OnConflictStrategy;
+import android.arch.persistence.room.Query;
+import android.arch.persistence.room.Update;
+import android.arch.persistence.room.integration.testapp.TestDatabase;
+import android.arch.persistence.room.integration.testapp.vo.AvgWeightByAge;
+import android.arch.persistence.room.integration.testapp.vo.User;
+import android.database.Cursor;
+
+import org.reactivestreams.Publisher;
+
+import java.util.Date;
+import java.util.List;
+import java.util.concurrent.Callable;
+
+import io.reactivex.Flowable;
+
+@SuppressWarnings("SameParameterValue")
+@Dao
+public abstract class UserDao {
+
+    private final TestDatabase mDatabase;
+
+    public UserDao(TestDatabase database) {
+        mDatabase = database;
+    }
+
+    @Query("select * from user where mName like :name")
+    public abstract List<User> findUsersByName(String name);
+
+    @Query("select * from user where mId = :id")
+    public abstract User load(int id);
+
+    @Query("select * from user where mId IN(:ids)")
+    public abstract User[] loadByIds(int... ids);
+
+    @Insert
+    public abstract void insert(User user);
+
+    @Insert(onConflict = OnConflictStrategy.REPLACE)
+    public abstract void insertOrReplace(User user);
+
+    @Delete
+    public abstract int delete(User user);
+
+    @Delete
+    public abstract int deleteAll(User[] users);
+
+    @Update
+    public abstract int update(User user);
+
+    @Update
+    public abstract int updateAll(List<User> users);
+
+    @Insert
+    public abstract void insertAll(User[] users);
+
+    @Query("select * from user where mAdmin = :isAdmin")
+    public abstract List<User> findByAdmin(boolean isAdmin);
+
+    @Query("delete from user where mAge > :age")
+    public abstract int deleteAgeGreaterThan(int age);
+
+    @Query("delete from user where mId IN(:uids)")
+    public abstract int deleteByUids(int... uids);
+
+    @Query("delete from user where mAge >= :min AND mAge <= :max")
+    public abstract int deleteByAgeRange(int min, int max);
+
+    @Query("update user set mName = :name where mId = :id")
+    public abstract int updateById(int id, String name);
+
+    @Query("update user set mId = mId + :amount")
+    public abstract void incrementIds(int amount);
+
+    @Query("select mId from user order by mId ASC")
+    public abstract List<Integer> loadIds();
+
+    @Query("select * from user where mId = :id")
+    public abstract LiveData<User> liveUserById(int id);
+
+    @Query("select * from user where mName LIKE '%' || :name || '%' ORDER BY mId DESC")
+    public abstract LiveData<List<User>> liveUsersListByName(String name);
+
+    @Query("select * from user where length(mName) = :length")
+    public abstract List<User> findByNameLength(int length);
+
+    @Query("select * from user where mAge = :age")
+    public abstract List<User> findByAge(int age);
+
+    @Query("select mAge, AVG(mWeight) from user GROUP BY mAge ORDER BY 2 DESC")
+    public abstract List<AvgWeightByAge> weightByAge();
+
+    @Query("select mAge, AVG(mWeight) from user GROUP BY mAge ORDER BY 2 DESC LIMIT 1")
+    public abstract LiveData<AvgWeightByAge> maxWeightByAgeGroup();
+
+    @Query("select * from user where mBirthday > :from AND mBirthday < :to")
+    public abstract List<User> findByBirthdayRange(Date from, Date to);
+
+    @Query("select mId from user where mId IN (:ids)")
+    public abstract Cursor findUsersAsCursor(int... ids);
+
+    @Query("select * from user where mId = :id")
+    public abstract Flowable<User> flowableUserById(int id);
+
+    @Query("select COUNT(*) from user")
+    public abstract Flowable<Integer> flowableCountUsers();
+
+    @Query("select COUNT(*) from user")
+    public abstract Publisher<Integer> publisherCountUsers();
+
+    @Query("SELECT mBirthday from User where mId = :id")
+    public abstract Date getBirthday(int id);
+
+    @Query("SELECT COUNT(*) from user")
+    public abstract int count();
+
+    public void insertBothByRunnable(final User a, final User b) {
+        mDatabase.runInTransaction(new Runnable() {
+            @Override
+            public void run() {
+                insert(a);
+                insert(b);
+            }
+        });
+    }
+
+    public int insertBothByCallable(final User a, final User b) {
+        return mDatabase.runInTransaction(new Callable<Integer>() {
+            @Override
+            public Integer call() throws Exception {
+                insert(a);
+                insert(b);
+                return 2;
+            }
+        });
+    }
+}
diff --git a/room/integration-tests/testapp/src/androidTest/java/android/arch/persistence/room/integration/testapp/dao/UserPetDao.java b/room/integration-tests/testapp/src/androidTest/java/android/arch/persistence/room/integration/testapp/dao/UserPetDao.java
new file mode 100644
index 0000000..7ba62a5
--- /dev/null
+++ b/room/integration-tests/testapp/src/androidTest/java/android/arch/persistence/room/integration/testapp/dao/UserPetDao.java
@@ -0,0 +1,72 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.arch.persistence.room.integration.testapp.dao;
+
+import android.arch.lifecycle.LiveData;
+import android.arch.persistence.room.Dao;
+import android.arch.persistence.room.Delete;
+import android.arch.persistence.room.Insert;
+import android.arch.persistence.room.Query;
+import android.arch.persistence.room.Update;
+import android.arch.persistence.room.integration.testapp.vo.Pet;
+import android.arch.persistence.room.integration.testapp.vo.User;
+import android.arch.persistence.room.integration.testapp.vo.UserAndAllPets;
+import android.arch.persistence.room.integration.testapp.vo.UserAndPet;
+import android.arch.persistence.room.integration.testapp.vo.UserAndPetNonNull;
+import android.arch.persistence.room.integration.testapp.vo.UserIdAndPetNames;
+import android.arch.persistence.room.integration.testapp.vo.UserWithPetsAndToys;
+
+import java.util.List;
+
+@Dao
+public interface UserPetDao {
+    @Query("SELECT * FROM User u, Pet p WHERE u.mId = p.mUserId")
+    List<UserAndPet> loadAll();
+
+    @Query("SELECT * FROM User u LEFT OUTER JOIN Pet p ON u.mId = p.mUserId")
+    List<UserAndPet> loadUsers();
+
+    @Query("SELECT * FROM User u LEFT OUTER JOIN Pet p ON u.mId = p.mUserId")
+    List<UserAndPetNonNull> loadUsersWithNonNullPet();
+
+    @Query("SELECT * FROM Pet p LEFT OUTER JOIN User u ON u.mId = p.mUserId")
+    List<UserAndPet> loadPets();
+
+    @Query("SELECT * FROM User u")
+    List<UserAndAllPets> loadAllUsersWithTheirPets();
+
+    @Query("SELECT * FROM User u")
+    List<UserIdAndPetNames> loadUserAndPetNames();
+
+    @Query("SELECT * FROM User u")
+    List<UserWithPetsAndToys> loadUserWithPetsAndToys();
+
+    @Query("SELECT * FROM User UNION ALL SELECT * FROM USER")
+    List<UserAndAllPets> unionByItself();
+
+    @Query("SELECT * FROM User u where u.mId = :userId")
+    LiveData<UserAndAllPets> liveUserWithPets(int userId);
+
+    @Insert
+    void insertUserAndPet(User user, Pet pet);
+
+    @Update
+    void updateUsersAndPets(User[] users, Pet[] pets);
+
+    @Delete
+    void delete2UsersAndPets(User user1, User user2, Pet[] pets);
+}
diff --git a/room/integration-tests/testapp/src/androidTest/java/android/arch/persistence/room/integration/testapp/migration/MigrationDb.java b/room/integration-tests/testapp/src/androidTest/java/android/arch/persistence/room/integration/testapp/migration/MigrationDb.java
new file mode 100644
index 0000000..bf1ad0d
--- /dev/null
+++ b/room/integration-tests/testapp/src/androidTest/java/android/arch/persistence/room/integration/testapp/migration/MigrationDb.java
@@ -0,0 +1,139 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.arch.persistence.room.integration.testapp.migration;
+
+import android.arch.persistence.db.SupportSQLiteDatabase;
+import android.arch.persistence.room.Dao;
+import android.arch.persistence.room.Database;
+import android.arch.persistence.room.Entity;
+import android.arch.persistence.room.ForeignKey;
+import android.arch.persistence.room.Ignore;
+import android.arch.persistence.room.Index;
+import android.arch.persistence.room.Insert;
+import android.arch.persistence.room.PrimaryKey;
+import android.arch.persistence.room.Query;
+import android.arch.persistence.room.RoomDatabase;
+import android.content.ContentValues;
+import android.database.sqlite.SQLiteDatabase;
+
+import java.util.List;
+
+@SuppressWarnings("WeakerAccess")
+@Database(version = MigrationDb.LATEST_VERSION,
+        entities = {MigrationDb.Entity1.class, MigrationDb.Entity2.class,
+                MigrationDb.Entity4.class})
+public abstract class MigrationDb extends RoomDatabase {
+    static final int LATEST_VERSION = 7;
+    abstract MigrationDao dao();
+    @Entity(indices = {@Index(value = "name", unique = true)})
+    static class Entity1 {
+        public static final String TABLE_NAME = "Entity1";
+        @PrimaryKey
+        public int id;
+        public String name;
+    }
+
+    @Entity
+    static class Entity2 {
+        public static final String TABLE_NAME = "Entity2";
+        @PrimaryKey
+        public int id;
+        public String addedInV3;
+        public String name;
+    }
+
+    @Entity
+    static class Entity3 { // added in version 4, removed at 6
+        public static final String TABLE_NAME = "Entity3";
+        @PrimaryKey
+        public int id;
+        @Ignore //removed at 5
+        public String removedInV5;
+        public String name;
+    }
+
+    @Entity(foreignKeys = {
+            @ForeignKey(entity = Entity1.class,
+            parentColumns = "name",
+            childColumns = "name",
+            deferred = true)})
+    static class Entity4 {
+        public static final String TABLE_NAME = "Entity4";
+        @PrimaryKey
+        public int id;
+        public String name;
+    }
+
+    @Dao
+    interface MigrationDao {
+        @Query("SELECT * from Entity1 ORDER BY id ASC")
+        List<Entity1> loadAllEntity1s();
+        @Query("SELECT * from Entity2 ORDER BY id ASC")
+        List<Entity2> loadAllEntity2s();
+        @Query("SELECT * from Entity2 ORDER BY id ASC")
+        List<Entity2Pojo> loadAllEntity2sAsPojo();
+        @Insert
+        void insert(Entity2... entity2);
+    }
+
+    static class Entity2Pojo extends Entity2 {
+    }
+
+    /**
+     * not a real dao because database will change.
+     */
+    static class Dao_V1 {
+        final SupportSQLiteDatabase mDb;
+
+        Dao_V1(SupportSQLiteDatabase db) {
+            mDb = db;
+        }
+
+        public void insertIntoEntity1(int id, String name) {
+            ContentValues values = new ContentValues();
+            values.put("id", id);
+            values.put("name", name);
+            long insertionId = mDb.insert(Entity1.TABLE_NAME,
+                    SQLiteDatabase.CONFLICT_REPLACE, values);
+            if (insertionId == -1) {
+                throw new RuntimeException("test sanity failure");
+            }
+        }
+    }
+
+    /**
+     * not a real dao because database will change.
+     */
+    static class Dao_V2 {
+        final SupportSQLiteDatabase mDb;
+
+        Dao_V2(SupportSQLiteDatabase db) {
+            mDb = db;
+        }
+
+        public void insertIntoEntity2(int id, String name) {
+            ContentValues values = new ContentValues();
+            values.put("id", id);
+            values.put("name", name);
+            long insertionId = mDb.insert(Entity2.TABLE_NAME,
+                    SQLiteDatabase.CONFLICT_REPLACE, values);
+            if (insertionId == -1) {
+                throw new RuntimeException("test sanity failure");
+            }
+        }
+    }
+}
diff --git a/room/integration-tests/testapp/src/androidTest/java/android/arch/persistence/room/integration/testapp/migration/MigrationTest.java b/room/integration-tests/testapp/src/androidTest/java/android/arch/persistence/room/integration/testapp/migration/MigrationTest.java
new file mode 100644
index 0000000..6e38b97
--- /dev/null
+++ b/room/integration-tests/testapp/src/androidTest/java/android/arch/persistence/room/integration/testapp/migration/MigrationTest.java
@@ -0,0 +1,303 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.arch.persistence.room.integration.testapp.migration;
+
+import static org.hamcrest.CoreMatchers.containsString;
+import static org.hamcrest.CoreMatchers.instanceOf;
+import static org.hamcrest.CoreMatchers.is;
+import static org.hamcrest.CoreMatchers.nullValue;
+import static org.hamcrest.MatcherAssert.assertThat;
+
+import android.arch.persistence.db.SupportSQLiteDatabase;
+import android.arch.persistence.db.framework.FrameworkSQLiteOpenHelperFactory;
+import android.arch.persistence.room.Room;
+import android.arch.persistence.room.migration.Migration;
+import android.arch.persistence.room.testing.MigrationTestHelper;
+import android.arch.persistence.room.util.TableInfo;
+import android.support.test.InstrumentationRegistry;
+import android.support.test.filters.SmallTest;
+import android.support.test.runner.AndroidJUnit4;
+
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.io.FileNotFoundException;
+import java.io.IOException;
+import java.util.List;
+
+/**
+ * Test custom database migrations.
+ */
+@RunWith(AndroidJUnit4.class)
+@SmallTest
+public class MigrationTest {
+    private static final String TEST_DB = "migration-test";
+    @Rule
+    public MigrationTestHelper helper;
+
+    public MigrationTest() {
+        helper = new MigrationTestHelper(InstrumentationRegistry.getInstrumentation(),
+                MigrationDb.class.getCanonicalName(), new FrameworkSQLiteOpenHelperFactory());
+    }
+
+    @Test
+    public void giveBadResource() throws IOException {
+        MigrationTestHelper helper = new MigrationTestHelper(
+                InstrumentationRegistry.getInstrumentation(),
+                "foo", new FrameworkSQLiteOpenHelperFactory());
+        try {
+            helper.createDatabase(TEST_DB, 1);
+            throw new AssertionError("must have failed with missing file exception");
+        } catch (FileNotFoundException exception) {
+            assertThat(exception.getMessage(), containsString("Cannot find"));
+        }
+    }
+
+    @Test
+    public void startInCurrentVersion() throws IOException {
+        SupportSQLiteDatabase db = helper.createDatabase(TEST_DB,
+                MigrationDb.LATEST_VERSION);
+        final MigrationDb.Dao_V1 dao = new MigrationDb.Dao_V1(db);
+        dao.insertIntoEntity1(2, "x");
+        db.close();
+        MigrationDb migrationDb = getLatestDb();
+        List<MigrationDb.Entity1> items = migrationDb.dao().loadAllEntity1s();
+        helper.closeWhenFinished(migrationDb);
+        assertThat(items.size(), is(1));
+    }
+
+    @Test
+    public void addTable() throws IOException {
+        SupportSQLiteDatabase db = helper.createDatabase(TEST_DB, 1);
+        final MigrationDb.Dao_V1 dao = new MigrationDb.Dao_V1(db);
+        dao.insertIntoEntity1(2, "foo");
+        dao.insertIntoEntity1(3, "bar");
+        db.close();
+        db = helper.runMigrationsAndValidate(TEST_DB, 2, true, MIGRATION_1_2);
+        new MigrationDb.Dao_V2(db).insertIntoEntity2(3, "blah");
+        db.close();
+        MigrationDb migrationDb = getLatestDb();
+        List<MigrationDb.Entity1> entity1s = migrationDb.dao().loadAllEntity1s();
+
+        assertThat(entity1s.size(), is(2));
+        MigrationDb.Entity2 entity2 = new MigrationDb.Entity2();
+        entity2.id = 2;
+        entity2.name = "bar";
+        // assert no error happens
+        migrationDb.dao().insert(entity2);
+        List<MigrationDb.Entity2> entity2s = migrationDb.dao().loadAllEntity2s();
+        assertThat(entity2s.size(), is(2));
+    }
+
+    private MigrationDb getLatestDb() {
+        MigrationDb db = Room.databaseBuilder(InstrumentationRegistry.getContext(),
+                MigrationDb.class, TEST_DB).addMigrations(ALL_MIGRATIONS).build();
+        // trigger open
+        db.beginTransaction();
+        db.endTransaction();
+        helper.closeWhenFinished(db);
+        return db;
+    }
+
+    @Test
+    public void addTableFailure() throws IOException {
+        testFailure(1, 2);
+    }
+
+    @Test
+    public void addColumnFailure() throws IOException {
+        SupportSQLiteDatabase db = helper.createDatabase(TEST_DB, 2);
+        db.close();
+        IllegalStateException caught = null;
+        try {
+            helper.runMigrationsAndValidate(TEST_DB, 3, true, new EmptyMigration(2, 3));
+        } catch (IllegalStateException ex) {
+            caught = ex;
+        }
+        assertThat(caught, instanceOf(IllegalStateException.class));
+    }
+
+    @Test
+    public void addColumn() throws IOException {
+        SupportSQLiteDatabase db = helper.createDatabase(TEST_DB, 2);
+        MigrationDb.Dao_V2 v2Dao = new MigrationDb.Dao_V2(db);
+        v2Dao.insertIntoEntity2(7, "blah");
+        db.close();
+        helper.runMigrationsAndValidate(TEST_DB, 3, true, MIGRATION_2_3);
+        // trigger open.
+        MigrationDb migrationDb = getLatestDb();
+        List<MigrationDb.Entity2> entity2s = migrationDb.dao().loadAllEntity2s();
+        assertThat(entity2s.size(), is(1));
+        assertThat(entity2s.get(0).name, is("blah"));
+        assertThat(entity2s.get(0).addedInV3, is(nullValue()));
+
+        List<MigrationDb.Entity2Pojo> entity2Pojos = migrationDb.dao().loadAllEntity2sAsPojo();
+        assertThat(entity2Pojos.size(), is(1));
+        assertThat(entity2Pojos.get(0).name, is("blah"));
+        assertThat(entity2Pojos.get(0).addedInV3, is(nullValue()));
+    }
+
+    @Test
+    public void failedToRemoveColumn() throws IOException {
+        testFailure(4, 5);
+    }
+
+    @Test
+    public void removeColumn() throws IOException {
+        helper.createDatabase(TEST_DB, 4);
+        final SupportSQLiteDatabase db = helper.runMigrationsAndValidate(TEST_DB,
+                5, true, MIGRATION_4_5);
+        final TableInfo info = TableInfo.read(db, MigrationDb.Entity3.TABLE_NAME);
+        assertThat(info.columns.size(), is(2));
+    }
+
+    @Test
+    public void dropTable() throws IOException {
+        helper.createDatabase(TEST_DB, 5);
+        final SupportSQLiteDatabase db = helper.runMigrationsAndValidate(TEST_DB,
+                6, true, MIGRATION_5_6);
+        final TableInfo info = TableInfo.read(db, MigrationDb.Entity3.TABLE_NAME);
+        assertThat(info.columns.size(), is(0));
+    }
+
+    @Test
+    public void failedToDropTable() throws IOException {
+        testFailure(5, 6);
+    }
+
+    @Test
+    public void failedToDropTableDontVerify() throws IOException {
+        helper.createDatabase(TEST_DB, 5);
+        final SupportSQLiteDatabase db = helper.runMigrationsAndValidate(TEST_DB,
+                6, false, new EmptyMigration(5, 6));
+        final TableInfo info = TableInfo.read(db, MigrationDb.Entity3.TABLE_NAME);
+        assertThat(info.columns.size(), is(2));
+    }
+
+    @Test
+    public void failedForeignKey() throws IOException {
+        final SupportSQLiteDatabase db = helper.createDatabase(TEST_DB, 6);
+        db.close();
+        Throwable throwable = null;
+        try {
+            helper.runMigrationsAndValidate(TEST_DB,
+                    7, false, new Migration(6, 7) {
+                        @Override
+                        public void migrate(SupportSQLiteDatabase database) {
+                            database.execSQL("CREATE TABLE Entity4 (`id` INTEGER, `name` TEXT,"
+                                    + " PRIMARY KEY(`id`))");
+                        }
+                    });
+        } catch (Throwable t) {
+            throwable = t;
+        }
+        assertThat(throwable, instanceOf(IllegalStateException.class));
+        //noinspection ConstantConditions
+        assertThat(throwable.getMessage(), containsString("Migration failed"));
+    }
+
+    @Test
+    public void newTableWithForeignKey() throws IOException {
+        helper.createDatabase(TEST_DB, 6);
+        final SupportSQLiteDatabase db = helper.runMigrationsAndValidate(TEST_DB,
+                7, false, MIGRATION_6_7);
+        final TableInfo info = TableInfo.read(db, MigrationDb.Entity4.TABLE_NAME);
+        assertThat(info.foreignKeys.size(), is(1));
+    }
+
+    private void testFailure(int startVersion, int endVersion) throws IOException {
+        final SupportSQLiteDatabase db = helper.createDatabase(TEST_DB, startVersion);
+        db.close();
+        Throwable throwable = null;
+        try {
+            helper.runMigrationsAndValidate(TEST_DB, endVersion, true,
+                    new EmptyMigration(startVersion, endVersion));
+        } catch (Throwable t) {
+            throwable = t;
+        }
+        assertThat(throwable, instanceOf(IllegalStateException.class));
+        assertThat(throwable.getMessage(), containsString("Migration failed"));
+    }
+
+    static final Migration MIGRATION_1_2 = new Migration(1, 2) {
+        @Override
+        public void migrate(SupportSQLiteDatabase database) {
+            database.execSQL("CREATE TABLE IF NOT EXISTS `Entity2` (`id` INTEGER,"
+                    + " `name` TEXT, PRIMARY KEY(`id`))");
+        }
+    };
+
+    static final Migration MIGRATION_2_3 = new Migration(2, 3) {
+        @Override
+        public void migrate(SupportSQLiteDatabase database) {
+            database.execSQL("ALTER TABLE " + MigrationDb.Entity2.TABLE_NAME
+                    + " ADD COLUMN addedInV3 TEXT");
+        }
+    };
+
+    static final Migration MIGRATION_3_4 = new Migration(3, 4) {
+        @Override
+        public void migrate(SupportSQLiteDatabase database) {
+            database.execSQL("CREATE TABLE IF NOT EXISTS `Entity3` (`id` INTEGER,"
+                    + " `removedInV5` TEXT, `name` TEXT, PRIMARY KEY(`id`))");
+        }
+    };
+
+    static final Migration MIGRATION_4_5 = new Migration(4, 5) {
+        @Override
+        public void migrate(SupportSQLiteDatabase database) {
+            database.execSQL("CREATE TABLE IF NOT EXISTS `Entity3_New` (`id` INTEGER,"
+                    + " `name` TEXT, PRIMARY KEY(`id`))");
+            database.execSQL("INSERT INTO Entity3_New(`id`, `name`) "
+                    + "SELECT `id`, `name` FROM Entity3");
+            database.execSQL("DROP TABLE Entity3");
+            database.execSQL("ALTER TABLE Entity3_New RENAME TO Entity3");
+        }
+    };
+
+    static final Migration MIGRATION_5_6 = new Migration(5, 6) {
+        @Override
+        public void migrate(SupportSQLiteDatabase database) {
+            database.execSQL("DROP TABLE " + MigrationDb.Entity3.TABLE_NAME);
+        }
+    };
+
+    static final Migration MIGRATION_6_7 = new Migration(6, 7) {
+        @Override
+        public void migrate(SupportSQLiteDatabase database) {
+            database.execSQL("CREATE TABLE IF NOT EXISTS " + MigrationDb.Entity4.TABLE_NAME
+                    + " (`id` INTEGER, `name` TEXT, PRIMARY KEY(`id`),"
+                    + " FOREIGN KEY(`name`) REFERENCES `Entity1`(`name`)"
+                    + " ON UPDATE NO ACTION ON DELETE NO ACTION DEFERRABLE INITIALLY DEFERRED)");
+        }
+    };
+
+    private static final Migration[] ALL_MIGRATIONS = new Migration[]{MIGRATION_1_2,
+            MIGRATION_2_3, MIGRATION_3_4, MIGRATION_4_5, MIGRATION_5_6, MIGRATION_6_7};
+
+    static final class EmptyMigration extends Migration {
+        EmptyMigration(int startVersion, int endVersion) {
+            super(startVersion, endVersion);
+        }
+
+        @Override
+        public void migrate(SupportSQLiteDatabase database) {
+            // do nothing
+        }
+    }
+}
diff --git a/room/integration-tests/testapp/src/androidTest/java/android/arch/persistence/room/integration/testapp/test/ConstructorTest.java b/room/integration-tests/testapp/src/androidTest/java/android/arch/persistence/room/integration/testapp/test/ConstructorTest.java
new file mode 100644
index 0000000..4e8fa97
--- /dev/null
+++ b/room/integration-tests/testapp/src/androidTest/java/android/arch/persistence/room/integration/testapp/test/ConstructorTest.java
@@ -0,0 +1,196 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.arch.persistence.room.integration.testapp.test;
+
+import static org.hamcrest.CoreMatchers.is;
+import static org.hamcrest.MatcherAssert.assertThat;
+
+import android.arch.persistence.room.Dao;
+import android.arch.persistence.room.Database;
+import android.arch.persistence.room.Embedded;
+import android.arch.persistence.room.Entity;
+import android.arch.persistence.room.Insert;
+import android.arch.persistence.room.PrimaryKey;
+import android.arch.persistence.room.Query;
+import android.arch.persistence.room.Room;
+import android.arch.persistence.room.RoomDatabase;
+import android.support.test.InstrumentationRegistry;
+import android.support.test.filters.SmallTest;
+import android.support.test.runner.AndroidJUnit4;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+@RunWith(AndroidJUnit4.class)
+@SuppressWarnings("SqlNoDataSourceInspection")
+@SmallTest
+public class ConstructorTest {
+    @Database(version = 1, entities = {FullConstructor.class, PartialConstructor.class},
+            exportSchema = false)
+    abstract static class MyDb extends RoomDatabase {
+        abstract MyDao dao();
+    }
+
+    @Dao
+    interface MyDao {
+        @Insert
+        void insertFull(FullConstructor... full);
+
+        @Query("SELECT * FROM fc WHERE a = :a")
+        FullConstructor loadFull(int a);
+
+        @Insert
+        void insertPartial(PartialConstructor... partial);
+
+        @Query("SELECT * FROM pc WHERE a = :a")
+        PartialConstructor loadPartial(int a);
+    }
+
+    @SuppressWarnings("WeakerAccess")
+    @Entity(tableName = "fc")
+    static class FullConstructor {
+        @PrimaryKey
+        public final int a;
+        public final int b;
+        @Embedded
+        public final MyEmbedded embedded;
+
+        FullConstructor(int a, int b, MyEmbedded embedded) {
+            this.a = a;
+            this.b = b;
+            this.embedded = embedded;
+        }
+
+        @Override
+        public boolean equals(Object o) {
+            if (this == o) return true;
+            if (o == null || getClass() != o.getClass()) return false;
+
+            FullConstructor that = (FullConstructor) o;
+
+            if (a != that.a) return false;
+            //noinspection SimplifiableIfStatement
+            if (b != that.b) return false;
+            return embedded != null ? embedded.equals(that.embedded)
+                    : that.embedded == null;
+        }
+
+        @Override
+        public int hashCode() {
+            int result = a;
+            result = 31 * result + b;
+            result = 31 * result + (embedded != null ? embedded.hashCode() : 0);
+            return result;
+        }
+    }
+
+    @SuppressWarnings("WeakerAccess")
+    @Entity(tableName = "pc")
+    static class PartialConstructor {
+        @PrimaryKey
+        public final int a;
+        public int b;
+        @Embedded
+        private MyEmbedded mEmbedded;
+
+        PartialConstructor(int a) {
+            this.a = a;
+        }
+
+        public MyEmbedded getEmbedded() {
+            return mEmbedded;
+        }
+
+        public void setEmbedded(MyEmbedded embedded) {
+            mEmbedded = embedded;
+        }
+
+        @Override
+        public boolean equals(Object o) {
+            if (this == o) return true;
+            if (o == null || getClass() != o.getClass()) return false;
+
+            PartialConstructor that = (PartialConstructor) o;
+
+            if (a != that.a) return false;
+            //noinspection SimplifiableIfStatement
+            if (b != that.b) return false;
+            return mEmbedded != null ? mEmbedded.equals(that.mEmbedded)
+                    : that.mEmbedded == null;
+        }
+
+        @Override
+        public int hashCode() {
+            int result = a;
+            result = 31 * result + b;
+            result = 31 * result + (mEmbedded != null ? mEmbedded.hashCode() : 0);
+            return result;
+        }
+    }
+
+    @SuppressWarnings("WeakerAccess")
+    static class MyEmbedded {
+        public final String text;
+
+        MyEmbedded(String text) {
+            this.text = text;
+        }
+
+        @Override
+        public boolean equals(Object o) {
+            if (this == o) return true;
+            if (o == null || getClass() != o.getClass()) return false;
+
+            MyEmbedded that = (MyEmbedded) o;
+
+            return text != null ? text.equals(that.text) : that.text == null;
+        }
+
+        @Override
+        public int hashCode() {
+            return text != null ? text.hashCode() : 0;
+        }
+    }
+
+    private MyDb mDb;
+    private MyDao mDao;
+
+    @Before
+    public void init() {
+        mDb = Room.inMemoryDatabaseBuilder(InstrumentationRegistry.getContext(), MyDb.class)
+                .build();
+        mDao = mDb.dao();
+    }
+
+    @Test
+    public void insertAndReadFullConstructor() {
+        FullConstructor inserted = new FullConstructor(1, 2, null);
+        mDao.insertFull(inserted);
+        final FullConstructor load = mDao.loadFull(1);
+        assertThat(load, is(inserted));
+    }
+
+    @Test
+    public void insertAndReadPartial() {
+        PartialConstructor item = new PartialConstructor(3);
+        item.b = 7;
+        mDao.insertPartial(item);
+        PartialConstructor load = mDao.loadPartial(3);
+        assertThat(load, is(item));
+    }
+}
diff --git a/room/integration-tests/testapp/src/androidTest/java/android/arch/persistence/room/integration/testapp/test/EmbeddedTest.java b/room/integration-tests/testapp/src/androidTest/java/android/arch/persistence/room/integration/testapp/test/EmbeddedTest.java
new file mode 100644
index 0000000..d680f3d
--- /dev/null
+++ b/room/integration-tests/testapp/src/androidTest/java/android/arch/persistence/room/integration/testapp/test/EmbeddedTest.java
@@ -0,0 +1,246 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.arch.persistence.room.integration.testapp.test;
+
+import static org.hamcrest.CoreMatchers.is;
+import static org.hamcrest.CoreMatchers.notNullValue;
+import static org.hamcrest.CoreMatchers.nullValue;
+import static org.hamcrest.MatcherAssert.assertThat;
+
+import android.arch.persistence.room.Room;
+import android.arch.persistence.room.integration.testapp.TestDatabase;
+import android.arch.persistence.room.integration.testapp.dao.PetCoupleDao;
+import android.arch.persistence.room.integration.testapp.dao.PetDao;
+import android.arch.persistence.room.integration.testapp.dao.SchoolDao;
+import android.arch.persistence.room.integration.testapp.dao.UserDao;
+import android.arch.persistence.room.integration.testapp.dao.UserPetDao;
+import android.arch.persistence.room.integration.testapp.vo.Coordinates;
+import android.arch.persistence.room.integration.testapp.vo.Pet;
+import android.arch.persistence.room.integration.testapp.vo.PetCouple;
+import android.arch.persistence.room.integration.testapp.vo.School;
+import android.arch.persistence.room.integration.testapp.vo.SchoolRef;
+import android.arch.persistence.room.integration.testapp.vo.User;
+import android.arch.persistence.room.integration.testapp.vo.UserAndPet;
+import android.arch.persistence.room.integration.testapp.vo.UserAndPetNonNull;
+import android.content.Context;
+import android.support.test.InstrumentationRegistry;
+import android.support.test.filters.SmallTest;
+import android.support.test.runner.AndroidJUnit4;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.concurrent.Callable;
+
+@SmallTest
+@RunWith(AndroidJUnit4.class)
+public class EmbeddedTest {
+    private UserDao mUserDao;
+    private PetDao mPetDao;
+    private UserPetDao mUserPetDao;
+    private SchoolDao mSchoolDao;
+    private PetCoupleDao mPetCoupleDao;
+
+    @Before
+    public void createDb() {
+        Context context = InstrumentationRegistry.getTargetContext();
+        TestDatabase db = Room.inMemoryDatabaseBuilder(context, TestDatabase.class).build();
+        mUserDao = db.getUserDao();
+        mPetDao = db.getPetDao();
+        mUserPetDao = db.getUserPetDao();
+        mSchoolDao = db.getSchoolDao();
+        mPetCoupleDao = db.getPetCoupleDao();
+    }
+
+    @Test
+    public void loadAll() {
+        Pet pet = TestUtil.createPet(1);
+        User user = TestUtil.createUser(2);
+        pet.setUserId(user.getId());
+        mUserDao.insert(user);
+        mPetDao.insertOrReplace(pet);
+        List<UserAndPet> all = mUserPetDao.loadAll();
+        assertThat(all.size(), is(1));
+        assertThat(all.get(0).getUser(), is(user));
+        assertThat(all.get(0).getPet(), is(pet));
+    }
+
+    @Test
+    public void loadFromUsers() {
+        Pet pet = TestUtil.createPet(1);
+        User user = TestUtil.createUser(2);
+        pet.setUserId(user.getId());
+        mUserDao.insert(user);
+        mPetDao.insertOrReplace(pet);
+        List<UserAndPet> all = mUserPetDao.loadUsers();
+        assertThat(all.size(), is(1));
+        assertThat(all.get(0).getUser(), is(user));
+        assertThat(all.get(0).getPet(), is(pet));
+    }
+
+    @Test
+    public void loadFromUsersWithNullPet() {
+        User user = TestUtil.createUser(2);
+        mUserDao.insert(user);
+        List<UserAndPet> all = mUserPetDao.loadUsers();
+        assertThat(all.size(), is(1));
+        assertThat(all.get(0).getUser(), is(user));
+        assertThat(all.get(0).getPet(), is(nullValue()));
+    }
+
+    @Test
+    public void loadFromUsersWithNonNullPet() {
+        User user = TestUtil.createUser(2);
+        mUserDao.insert(user);
+        List<UserAndPetNonNull> all = mUserPetDao.loadUsersWithNonNullPet();
+        assertThat(all.size(), is(1));
+        assertThat(all.get(0).getUser(), is(user));
+        assertThat(all.get(0).getPet(), is(new Pet()));
+    }
+
+    @Test
+    public void loadFromPets() {
+        Pet pet = TestUtil.createPet(1);
+        User user = TestUtil.createUser(2);
+        pet.setUserId(user.getId());
+        mUserDao.insert(user);
+        mPetDao.insertOrReplace(pet);
+        List<UserAndPet> all = mUserPetDao.loadPets();
+        assertThat(all.size(), is(1));
+        assertThat(all.get(0).getUser(), is(user));
+        assertThat(all.get(0).getPet(), is(pet));
+    }
+
+    @Test
+    public void loadFromPetsWithNullUser() {
+        Pet pet = TestUtil.createPet(1);
+        mPetDao.insertOrReplace(pet);
+        List<UserAndPet> all = mUserPetDao.loadPets();
+        assertThat(all.size(), is(1));
+        assertThat(all.get(0).getUser(), is(nullValue()));
+        assertThat(all.get(0).getPet(), is(pet));
+    }
+
+    @Test
+    public void findSchoolByStreet() {
+        School school = TestUtil.createSchool(3, 5);
+        school.getAddress().setStreet("foo");
+        mSchoolDao.insert(school);
+        List<School> result = mSchoolDao.findByStreet("foo");
+        assertThat(result.size(), is(1));
+        assertThat(result.get(0), is(school));
+    }
+
+    @Test
+    public void loadSubFieldsAsPojo() throws Exception {
+        loadSubFieldsTest(new Callable<List<School>>() {
+            @Override
+            public List<School> call() throws Exception {
+                List<School> result = new ArrayList<>();
+                for (SchoolRef ref : mSchoolDao.schoolAndManagerNamesAsPojo()) {
+                    result.add(ref);
+                }
+                return result;
+            }
+        });
+    }
+
+    @Test
+    public void loadSubFieldsAsEntity() throws Exception {
+        loadSubFieldsTest(new Callable<List<School>>() {
+            @Override
+            public List<School> call() throws Exception {
+                return mSchoolDao.schoolAndManagerNames();
+            }
+        });
+    }
+
+    public void loadSubFieldsTest(Callable<List<School>> loader) throws Exception {
+        School school = TestUtil.createSchool(3, 5);
+        school.setName("MTV High");
+        school.getManager().setName("chet");
+        mSchoolDao.insert(school);
+
+        School school2 = TestUtil.createSchool(4, 6);
+        school2.setName("MTV Low");
+        school2.setManager(null);
+        mSchoolDao.insert(school2);
+
+        List<School> schools = loader.call();
+        assertThat(schools.size(), is(2));
+        assertThat(schools.get(0).getName(), is("MTV High"));
+        assertThat(schools.get(1).getName(), is("MTV Low"));
+        assertThat(schools.get(0).address, nullValue());
+        assertThat(schools.get(1).address, nullValue());
+        assertThat(schools.get(0).getManager(), notNullValue());
+        assertThat(schools.get(1).getManager(), nullValue());
+        assertThat(schools.get(0).getManager().getName(), is("chet"));
+    }
+
+    @Test
+    public void loadNestedSub() {
+        School school = TestUtil.createSchool(3, 5);
+        school.getAddress().getCoordinates().lat = 3.;
+        school.getAddress().getCoordinates().lng = 4.;
+        mSchoolDao.insert(school);
+        Coordinates coordinates = mSchoolDao.loadCoordinates(3);
+        assertThat(coordinates.lat, is(3.));
+        assertThat(coordinates.lng, is(4.));
+
+        School asSchool = mSchoolDao.loadCoordinatesAsSchool(3);
+        assertThat(asSchool.address.getCoordinates().lat, is(3.));
+        assertThat(asSchool.address.getCoordinates().lng, is(4.));
+        // didn't as for it so don't load
+        assertThat(asSchool.getManager(), nullValue());
+        assertThat(asSchool.address.getStreet(), nullValue());
+    }
+
+    @Test
+    public void sameFieldType() {
+        Pet male = TestUtil.createPet(3);
+        Pet female = TestUtil.createPet(5);
+        PetCouple petCouple = new PetCouple();
+        petCouple.id = "foo";
+        petCouple.male = male;
+        petCouple.setFemale(female);
+        mPetCoupleDao.insert(petCouple);
+        List<PetCouple> petCouples = mPetCoupleDao.loadAll();
+        assertThat(petCouples.size(), is(1));
+        PetCouple loaded = petCouples.get(0);
+        assertThat(loaded.id, is("foo"));
+        assertThat(loaded.male, is(male));
+        assertThat(loaded.getFemale(), is(female));
+    }
+
+    @Test
+    public void sameFieldOneNull() {
+        Pet loneWolf = TestUtil.createPet(3);
+        PetCouple petCouple = new PetCouple();
+        petCouple.id = "foo";
+        petCouple.male = loneWolf;
+        mPetCoupleDao.insert(petCouple);
+        List<PetCouple> petCouples = mPetCoupleDao.loadAll();
+        assertThat(petCouples.size(), is(1));
+        PetCouple loaded = petCouples.get(0);
+        assertThat(loaded.id, is("foo"));
+        assertThat(loaded.male, is(loneWolf));
+        assertThat(loaded.getFemale(), is(nullValue()));
+    }
+}
diff --git a/room/integration-tests/testapp/src/androidTest/java/android/arch/persistence/room/integration/testapp/test/ForeignKeyTest.java b/room/integration-tests/testapp/src/androidTest/java/android/arch/persistence/room/integration/testapp/test/ForeignKeyTest.java
new file mode 100644
index 0000000..502d1f8
--- /dev/null
+++ b/room/integration-tests/testapp/src/androidTest/java/android/arch/persistence/room/integration/testapp/test/ForeignKeyTest.java
@@ -0,0 +1,425 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.arch.persistence.room.integration.testapp.test;
+
+import static org.hamcrest.CoreMatchers.both;
+import static org.hamcrest.CoreMatchers.containsString;
+import static org.hamcrest.CoreMatchers.either;
+import static org.hamcrest.CoreMatchers.equalTo;
+import static org.hamcrest.CoreMatchers.instanceOf;
+import static org.hamcrest.CoreMatchers.notNullValue;
+import static org.hamcrest.CoreMatchers.nullValue;
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.hamcrest.core.Is.is;
+
+import android.arch.persistence.room.Dao;
+import android.arch.persistence.room.Database;
+import android.arch.persistence.room.Delete;
+import android.arch.persistence.room.Entity;
+import android.arch.persistence.room.ForeignKey;
+import android.arch.persistence.room.Ignore;
+import android.arch.persistence.room.Index;
+import android.arch.persistence.room.Insert;
+import android.arch.persistence.room.PrimaryKey;
+import android.arch.persistence.room.Query;
+import android.arch.persistence.room.Room;
+import android.arch.persistence.room.RoomDatabase;
+import android.database.sqlite.SQLiteException;
+import android.support.test.InstrumentationRegistry;
+import android.support.test.filters.SmallTest;
+import android.support.test.runner.AndroidJUnit4;
+
+import org.hamcrest.Matcher;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.util.Locale;
+
+@RunWith(AndroidJUnit4.class)
+@SmallTest
+public class ForeignKeyTest {
+    @Database(version = 1, entities = {A.class, B.class, C.class, D.class, E.class},
+            exportSchema = false)
+    abstract static class ForeignKeyDb extends RoomDatabase {
+        abstract FkDao dao();
+    }
+
+    @SuppressWarnings({"SqlNoDataSourceInspection", "SameParameterValue"})
+    @Dao
+    interface FkDao {
+        @Insert
+        void insert(A... a);
+
+        @Insert
+        void insert(B... b);
+
+        @Insert
+        void insert(C... c);
+
+        @Insert
+        void insert(D... d);
+
+        @Query("SELECT * FROM A WHERE id = :id")
+        A loadA(int id);
+
+        @Query("SELECT * FROM B WHERE id = :id")
+        B loadB(int id);
+
+        @Query("SELECT * FROM C WHERE id = :id")
+        C loadC(int id);
+
+        @Query("SELECT * FROM D WHERE id = :id")
+        D loadD(int id);
+
+        @Query("SELECT * FROM E WHERE id = :id")
+        E loadE(int id);
+
+        @Delete
+        void delete(A... a);
+
+        @Delete
+        void delete(B... b);
+
+        @Delete
+        void delete(C... c);
+
+        @Query("UPDATE A SET name = :newName WHERE id = :id")
+        void changeNameA(int id, String newName);
+
+        @Insert
+        void insert(E... e);
+
+
+    }
+
+    @Entity(indices = {@Index(value = "name", unique = true),
+            @Index(value = {"name", "lastName"}, unique = true)})
+    static class A {
+        @PrimaryKey(autoGenerate = true)
+        public int id;
+        public String name;
+        public String lastName;
+
+        A(String name) {
+            this.name = name;
+        }
+
+        @Ignore
+        A(String name, String lastName) {
+            this.name = name;
+            this.lastName = lastName;
+        }
+    }
+
+    @SuppressWarnings("WeakerAccess")
+    @Entity(foreignKeys = {
+            @ForeignKey(entity = A.class,
+                    parentColumns = "name",
+                    childColumns = "aName")})
+
+    static class B {
+        @PrimaryKey(autoGenerate = true)
+        public int id;
+        public String aName;
+
+        B(String aName) {
+            this.aName = aName;
+        }
+    }
+
+    @SuppressWarnings("WeakerAccess")
+    @Entity(foreignKeys = {
+            @ForeignKey(entity = A.class,
+                    parentColumns = "name",
+                    childColumns = "aName",
+                    deferred = true)})
+    static class C {
+        @PrimaryKey(autoGenerate = true)
+        public int id;
+        public String aName;
+
+        C(String aName) {
+            this.aName = aName;
+        }
+    }
+
+    @SuppressWarnings("WeakerAccess")
+    @Entity(foreignKeys = {
+            @ForeignKey(entity = A.class,
+                    parentColumns = "name",
+                    childColumns = "aName",
+                    onDelete = ForeignKey.CASCADE,
+                    onUpdate = ForeignKey.CASCADE)})
+    static class D {
+        @PrimaryKey(autoGenerate = true)
+        public int id;
+        public String aName;
+
+        D(String aName) {
+            this.aName = aName;
+        }
+    }
+
+    @SuppressWarnings("WeakerAccess")
+    @Entity(foreignKeys = {
+            @ForeignKey(entity = A.class,
+                    parentColumns = {"name", "lastName"},
+                    childColumns = {"aName", "aLastName"},
+                    onDelete = ForeignKey.SET_NULL,
+                    onUpdate = ForeignKey.CASCADE)})
+    static class E {
+        @PrimaryKey(autoGenerate = true)
+        public int id;
+        public String aName;
+        public String aLastName;
+
+        E() {
+        }
+
+        @Ignore
+        E(String aName, String aLastName) {
+            this.aName = aName;
+            this.aLastName = aLastName;
+        }
+    }
+
+
+    private ForeignKeyDb mDb;
+    private FkDao mDao;
+
+    @Before
+    public void openDb() {
+        mDb = Room.inMemoryDatabaseBuilder(InstrumentationRegistry.getContext(),
+                ForeignKeyDb.class).build();
+        mDao = mDb.dao();
+    }
+
+    @Test
+    public void simpleForeignKeyFailure() {
+        Throwable t = catchException(new ThrowingRunnable() {
+            @Override
+            public void run() throws Exception {
+                mDao.insert(new B("foo"));
+            }
+        });
+        assertThat(t, instanceOf(SQLiteException.class));
+        assertThat(t.getMessage().toUpperCase(Locale.US), is(foreignKeyErrorMessage()));
+    }
+
+    @Test
+    public void simpleForeignKeyDeferredFailure() {
+        Throwable t = catchException(new ThrowingRunnable() {
+            @Override
+            public void run() throws Exception {
+                mDao.insert(new C("foo"));
+            }
+        });
+        assertThat(t, instanceOf(SQLiteException.class));
+        assertThat(t.getMessage().toUpperCase(Locale.US), is(foreignKeyErrorMessage()));
+    }
+
+    @Test
+    public void immediateForeignKeyFailure() {
+        Throwable t = catchException(new ThrowingRunnable() {
+            @Override
+            public void run() throws Exception {
+                try {
+                    mDb.beginTransaction();
+                    mDao.insert(new B("foo"));
+                    mDao.insert(new A("foo"));
+                    mDb.setTransactionSuccessful();
+                } finally {
+                    mDb.endTransaction();
+                }
+            }
+        });
+        assertThat(t, instanceOf(SQLiteException.class));
+    }
+
+    @Test
+    public void deferredForeignKeySuccess() {
+        try {
+            mDb.beginTransaction();
+            mDao.insert(new C("foo"));
+            mDao.insert(new A("foo"));
+            mDb.setTransactionSuccessful();
+        } finally {
+            mDb.endTransaction();
+        }
+        assertThat(mDao.loadA(1), notNullValue());
+        assertThat(mDao.loadC(1), notNullValue());
+    }
+
+    @Test
+    public void onDelete_noAction() {
+        mDao.insert(new A("a1"));
+        final A a = mDao.loadA(1);
+        mDao.insert(new B("a1"));
+        Throwable t = catchException(new ThrowingRunnable() {
+            @Override
+            public void run() throws Exception {
+                mDao.delete(a);
+            }
+        });
+        assertThat(t, instanceOf(SQLiteException.class));
+        assertThat(t.getMessage().toUpperCase(Locale.US), is(foreignKeyErrorMessage()));
+    }
+
+    @Test
+    public void onDelete_noAction_withTransaction() {
+        mDao.insert(new A("a1"));
+        final A a = mDao.loadA(1);
+        mDao.insert(new B("a1"));
+        final B b = mDao.loadB(1);
+        Throwable t = catchException(new ThrowingRunnable() {
+            @Override
+            public void run() throws Exception {
+                deleteInTransaction(a, b);
+            }
+        });
+        assertThat(t, instanceOf(SQLiteException.class));
+        assertThat(t.getMessage().toUpperCase(Locale.US), is(foreignKeyErrorMessage()));
+    }
+
+    @Test
+    public void onDelete_noAction_deferred() {
+        mDao.insert(new A("a1"));
+        final A a = mDao.loadA(1);
+        mDao.insert(new C("a1"));
+        Throwable t = catchException(new ThrowingRunnable() {
+            @Override
+            public void run() throws Exception {
+                mDao.delete(a);
+            }
+        });
+        assertThat(t, instanceOf(SQLiteException.class));
+        assertThat(t.getMessage().toUpperCase(Locale.US), is(foreignKeyErrorMessage()));
+    }
+
+    @Test
+    public void onDelete_noAction__deferredWithTransaction() {
+        mDao.insert(new A("a1"));
+        final A a = mDao.loadA(1);
+        mDao.insert(new C("a1"));
+        final C c = mDao.loadC(1);
+        deleteInTransaction(a, c);
+    }
+
+    @Test
+    public void onDelete_cascade() {
+        mDao.insert(new A("a1"));
+        final A a = mDao.loadA(1);
+        mDao.insert(new D("a1"));
+        final D d = mDao.loadD(1);
+        assertThat("test sanity", d, notNullValue());
+        mDao.delete(a);
+        assertThat(mDao.loadD(1), nullValue());
+    }
+
+    @Test
+    public void onUpdate_cascade() {
+        mDao.insert(new A("a1"));
+        mDao.insert(new D("a1"));
+        final D d = mDao.loadD(1);
+        assertThat("test sanity", d, notNullValue());
+        mDao.changeNameA(1, "bla");
+        assertThat(mDao.loadD(1).aName, equalTo("bla"));
+        assertThat(mDao.loadA(1).name, equalTo("bla"));
+    }
+
+    @Test
+    public void multipleReferences() {
+        mDao.insert(new A("a1", "a2"));
+        final A a = mDao.loadA(1);
+        assertThat("test sanity", a, notNullValue());
+        Throwable t = catchException(new ThrowingRunnable() {
+            @Override
+            public void run() throws Exception {
+                mDao.insert(new E("a1", "dsa"));
+            }
+        });
+        assertThat(t.getMessage().toUpperCase(Locale.US), is(foreignKeyErrorMessage()));
+    }
+
+    @Test
+    public void onDelete_setNull_multipleReferences() {
+        mDao.insert(new A("a1", "a2"));
+        final A a = mDao.loadA(1);
+        mDao.insert(new E("a1", "a2"));
+        assertThat(mDao.loadE(1), notNullValue());
+        mDao.delete(a);
+        E e = mDao.loadE(1);
+        assertThat(e, notNullValue());
+        assertThat(e.aName, nullValue());
+        assertThat(e.aLastName, nullValue());
+    }
+
+    @Test
+    public void onUpdate_cascade_multipleReferences() {
+        mDao.insert(new A("a1", "a2"));
+        final A a = mDao.loadA(1);
+        mDao.insert(new E("a1", "a2"));
+        assertThat(mDao.loadE(1), notNullValue());
+        mDao.changeNameA(1, "foo");
+        assertThat(mDao.loadE(1), notNullValue());
+        assertThat(mDao.loadE(1).aName, equalTo("foo"));
+        assertThat(mDao.loadE(1).aLastName, equalTo("a2"));
+    }
+
+    private static Matcher<String> foreignKeyErrorMessage() {
+        return either(containsString("FOREIGN KEY"))
+                .or(both(containsString("CODE 19")).and(containsString("CONSTRAINT FAILED")));
+    }
+
+    @SuppressWarnings("Duplicates")
+    private void deleteInTransaction(A a, B b) {
+        mDb.beginTransaction();
+        try {
+            mDao.delete(a);
+            mDao.delete(b);
+            mDb.setTransactionSuccessful();
+        } finally {
+            mDb.endTransaction();
+        }
+    }
+
+    @SuppressWarnings("Duplicates")
+    private void deleteInTransaction(A a, C c) {
+        mDb.beginTransaction();
+        try {
+            mDao.delete(a);
+            mDao.delete(c);
+            mDb.setTransactionSuccessful();
+        } finally {
+            mDb.endTransaction();
+        }
+    }
+
+    private static Throwable catchException(ThrowingRunnable throwingRunnable) {
+        try {
+            throwingRunnable.run();
+        } catch (Throwable t) {
+            return t;
+        }
+        throw new RuntimeException("didn't throw an exception");
+    }
+
+    private interface ThrowingRunnable {
+        void run() throws Exception;
+    }
+}
diff --git a/room/integration-tests/testapp/src/androidTest/java/android/arch/persistence/room/integration/testapp/test/IdentityDetectionTest.java b/room/integration-tests/testapp/src/androidTest/java/android/arch/persistence/room/integration/testapp/test/IdentityDetectionTest.java
new file mode 100644
index 0000000..1c1b503
--- /dev/null
+++ b/room/integration-tests/testapp/src/androidTest/java/android/arch/persistence/room/integration/testapp/test/IdentityDetectionTest.java
@@ -0,0 +1,106 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.arch.persistence.room.integration.testapp.test;
+
+import static org.hamcrest.CoreMatchers.instanceOf;
+import static org.hamcrest.CoreMatchers.is;
+import static org.hamcrest.MatcherAssert.assertThat;
+
+import android.support.test.InstrumentationRegistry;
+import android.support.test.filters.SmallTest;
+import android.support.test.runner.AndroidJUnit4;
+import android.util.Log;
+
+import android.arch.persistence.db.SupportSQLiteDatabase;
+import android.arch.persistence.room.Room;
+import android.arch.persistence.room.integration.testapp.TestDatabase;
+import android.arch.persistence.room.integration.testapp.vo.User;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.io.File;
+
+@RunWith(AndroidJUnit4.class)
+@SmallTest
+public class IdentityDetectionTest {
+    static final String TAG = "IdentityDetectionTest";
+    static final String DB_FILE_NAME = "identity_test_db";
+    TestDatabase mTestDatabase;
+    @Before
+    public void createTestDatabase() {
+        deleteDbFile();
+    }
+
+    @Test
+    public void reOpenWithoutIssues() {
+        openDb();
+        mTestDatabase.getUserDao().insert(TestUtil.createUser(3));
+        closeDb();
+        openDb();
+        User[] users = mTestDatabase.getUserDao().loadByIds(3);
+        assertThat(users.length, is(1));
+    }
+
+    @Test
+    public void reOpenChangedHash() {
+        openDb();
+        mTestDatabase.getUserDao().insert(TestUtil.createUser(3));
+        // change the hash
+        SupportSQLiteDatabase db = mTestDatabase.getOpenHelper().getWritableDatabase();
+        db.execSQL("UPDATE " + Room.MASTER_TABLE_NAME + " SET `identity_hash` = ?"
+                + " WHERE id = 42", new String[]{"bad hash"});
+        closeDb();
+        Throwable[] exceptions = new Throwable[1];
+        try {
+            openDb();
+            mTestDatabase.getUserDao().loadByIds(3);
+        } catch (Throwable t) {
+            exceptions[0] = t;
+            mTestDatabase = null;
+        }
+        assertThat(exceptions[0], instanceOf(IllegalStateException.class));
+    }
+
+    private void closeDb() {
+        mTestDatabase.getOpenHelper().close();
+    }
+
+    private void openDb() {
+        mTestDatabase = Room.databaseBuilder(InstrumentationRegistry.getTargetContext(),
+                TestDatabase.class, DB_FILE_NAME).build();
+    }
+
+    @After
+    public void clear() {
+        try {
+            if (mTestDatabase != null) {
+                closeDb();
+            }
+            deleteDbFile();
+        } finally {
+            Log.e(TAG, "could not close test database");
+        }
+    }
+
+    private void deleteDbFile() {
+        File testDb = InstrumentationRegistry.getTargetContext().getDatabasePath(DB_FILE_NAME);
+        testDb.delete();
+    }
+}
diff --git a/room/integration-tests/testapp/src/androidTest/java/android/arch/persistence/room/integration/testapp/test/IndexingTest.java b/room/integration-tests/testapp/src/androidTest/java/android/arch/persistence/room/integration/testapp/test/IndexingTest.java
new file mode 100644
index 0000000..16f916b
--- /dev/null
+++ b/room/integration-tests/testapp/src/androidTest/java/android/arch/persistence/room/integration/testapp/test/IndexingTest.java
@@ -0,0 +1,97 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.arch.persistence.room.integration.testapp.test;
+
+import static org.hamcrest.CoreMatchers.is;
+import static org.hamcrest.MatcherAssert.assertThat;
+
+import android.content.Context;
+import android.support.test.InstrumentationRegistry;
+import android.support.test.filters.SmallTest;
+import android.support.test.runner.AndroidJUnit4;
+
+import android.arch.persistence.room.ColumnInfo;
+import android.arch.persistence.room.Dao;
+import android.arch.persistence.room.Database;
+import android.arch.persistence.room.Entity;
+import android.arch.persistence.room.Index;
+import android.arch.persistence.room.PrimaryKey;
+import android.arch.persistence.room.Query;
+import android.arch.persistence.room.Room;
+import android.arch.persistence.room.RoomDatabase;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.util.List;
+
+@SmallTest
+@RunWith(AndroidJUnit4.class)
+public class IndexingTest {
+    @Entity(
+            tableName = "foo_table",
+            indices = {
+                    @Index({"field1", "field2"}),
+                    @Index(value = {"field2", "mId"}, unique = true),
+                    @Index(value = {"field2"}, unique = true, name = "customIndex"),
+            })
+    static class Entity1 {
+        @PrimaryKey
+        public int mId;
+        public String field1;
+        public String field2;
+        @ColumnInfo(index = true, name = "my_field")
+        public String field3;
+    }
+
+    static class IndexInfo {
+        public String name;
+        @ColumnInfo(name = "tbl_name")
+        public String tableName;
+        public String sql;
+    }
+
+    @Dao
+    public interface SqlMasterDao {
+        @Query("SELECT * FROM sqlite_master WHERE type = 'index'")
+        List<IndexInfo> loadIndices();
+    }
+
+    @Database(entities = {Entity1.class}, version = 1, exportSchema = false)
+    abstract static class IndexingDb extends RoomDatabase {
+        abstract SqlMasterDao sqlMasterDao();
+    }
+
+    @Test
+    public void verifyIndices() {
+        Context context = InstrumentationRegistry.getTargetContext();
+        IndexingDb db = Room.inMemoryDatabaseBuilder(context, IndexingDb.class).build();
+        List<IndexInfo> indices = db.sqlMasterDao().loadIndices();
+        assertThat(indices.size(), is(4));
+        for (IndexInfo info : indices) {
+            assertThat(info.tableName, is("foo_table"));
+        }
+        assertThat(indices.get(0).sql, is("CREATE INDEX `index_foo_table_field1_field2`"
+                + " ON `foo_table` (`field1`, `field2`)"));
+        assertThat(indices.get(1).sql, is("CREATE UNIQUE INDEX `index_foo_table_field2_mId`"
+                + " ON `foo_table` (`field2`, `mId`)"));
+        assertThat(indices.get(2).sql, is("CREATE UNIQUE INDEX `customIndex`"
+                + " ON `foo_table` (`field2`)"));
+        assertThat(indices.get(3).sql, is("CREATE INDEX `index_foo_table_my_field`"
+                + " ON `foo_table` (`my_field`)"));
+    }
+}
diff --git a/room/integration-tests/testapp/src/androidTest/java/android/arch/persistence/room/integration/testapp/test/InvalidationTest.java b/room/integration-tests/testapp/src/androidTest/java/android/arch/persistence/room/integration/testapp/test/InvalidationTest.java
new file mode 100644
index 0000000..b6efe69
--- /dev/null
+++ b/room/integration-tests/testapp/src/androidTest/java/android/arch/persistence/room/integration/testapp/test/InvalidationTest.java
@@ -0,0 +1,186 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.arch.persistence.room.integration.testapp.test;
+
+import static org.hamcrest.CoreMatchers.hasItem;
+import static org.hamcrest.CoreMatchers.is;
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.hamcrest.collection.IsCollectionWithSize.hasSize;
+
+import android.arch.core.executor.AppToolkitTaskExecutor;
+import android.arch.core.executor.TaskExecutor;
+import android.arch.persistence.room.InvalidationTracker;
+import android.arch.persistence.room.Room;
+import android.arch.persistence.room.integration.testapp.TestDatabase;
+import android.arch.persistence.room.integration.testapp.dao.UserDao;
+import android.arch.persistence.room.integration.testapp.vo.User;
+import android.content.Context;
+import android.os.Handler;
+import android.os.Looper;
+import android.support.annotation.NonNull;
+import android.support.test.InstrumentationRegistry;
+import android.support.test.filters.SmallTest;
+import android.support.test.runner.AndroidJUnit4;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.util.Set;
+import java.util.concurrent.Callable;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.ExecutionException;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.Executors;
+import java.util.concurrent.FutureTask;
+import java.util.concurrent.TimeUnit;
+
+/**
+ * Tests invalidation tracking.
+ */
+@SmallTest
+@RunWith(AndroidJUnit4.class)
+public class InvalidationTest {
+    private UserDao mUserDao;
+    private TestDatabase mDb;
+
+    @Before
+    public void createDb() {
+        Context context = InstrumentationRegistry.getTargetContext();
+        mDb = Room.inMemoryDatabaseBuilder(context, TestDatabase.class).build();
+        mUserDao = mDb.getUserDao();
+    }
+
+    @Before
+    public void setSingleThreadedIO() {
+        AppToolkitTaskExecutor.getInstance().setDelegate(new TaskExecutor() {
+            ExecutorService mIOExecutor = Executors.newSingleThreadExecutor();
+            Handler mHandler = new Handler(Looper.getMainLooper());
+
+            @Override
+            public void executeOnDiskIO(Runnable runnable) {
+                mIOExecutor.execute(runnable);
+            }
+
+            @Override
+            public void postToMainThread(Runnable runnable) {
+                mHandler.post(runnable);
+            }
+
+            @Override
+            public boolean isMainThread() {
+                return Thread.currentThread() == Looper.getMainLooper().getThread();
+            }
+        });
+    }
+
+    @After
+    public void clearExecutor() {
+        AppToolkitTaskExecutor.getInstance().setDelegate(null);
+    }
+
+    private void waitUntilIOThreadIsIdle() {
+        FutureTask<Void> future = new FutureTask<>(new Callable<Void>() {
+            @Override
+            public Void call() throws Exception {
+                return null;
+            }
+        });
+        AppToolkitTaskExecutor.getInstance().executeOnDiskIO(future);
+        //noinspection TryWithIdenticalCatches
+        try {
+            future.get();
+        } catch (InterruptedException e) {
+            throw new RuntimeException(e);
+        } catch (ExecutionException e) {
+            throw new RuntimeException(e);
+        }
+    }
+
+    @Test
+    public void testInvalidationOnUpdate() throws InterruptedException {
+        User user = TestUtil.createUser(3);
+        mUserDao.insert(user);
+        LatchObserver observer = new LatchObserver(1, "User");
+        mDb.getInvalidationTracker().addObserver(observer);
+        mUserDao.updateById(3, "foo2");
+        waitUntilIOThreadIsIdle();
+        assertThat(observer.await(), is(true));
+        assertThat(observer.getInvalidatedTables(), hasSize(1));
+        assertThat(observer.getInvalidatedTables(), hasItem("User"));
+    }
+
+    @Test
+    public void testInvalidationOnDelete() throws InterruptedException {
+        User user = TestUtil.createUser(3);
+        mUserDao.insert(user);
+        LatchObserver observer = new LatchObserver(1, "User");
+        mDb.getInvalidationTracker().addObserver(observer);
+        mUserDao.delete(user);
+        waitUntilIOThreadIsIdle();
+        assertThat(observer.await(), is(true));
+        assertThat(observer.getInvalidatedTables(), hasSize(1));
+        assertThat(observer.getInvalidatedTables(), hasItem("User"));
+    }
+
+    @Test
+    public void testInvalidationOnInsert() throws InterruptedException {
+        LatchObserver observer = new LatchObserver(1, "User");
+        mDb.getInvalidationTracker().addObserver(observer);
+        mUserDao.insert(TestUtil.createUser(3));
+        waitUntilIOThreadIsIdle();
+        assertThat(observer.await(), is(true));
+        assertThat(observer.getInvalidatedTables(), hasSize(1));
+        assertThat(observer.getInvalidatedTables(), hasItem("User"));
+    }
+
+    @Test
+    public void testDontInvalidateOnLateInsert() throws InterruptedException {
+        LatchObserver observer = new LatchObserver(1, "User");
+        mUserDao.insert(TestUtil.createUser(3));
+        waitUntilIOThreadIsIdle();
+        mDb.getInvalidationTracker().addObserver(observer);
+        waitUntilIOThreadIsIdle();
+        assertThat(observer.await(), is(false));
+    }
+
+    private static class LatchObserver extends InvalidationTracker.Observer {
+        CountDownLatch mLatch;
+
+        private Set<String> mInvalidatedTables;
+
+        LatchObserver(int permits, String... tables) {
+            super(tables);
+            mLatch = new CountDownLatch(permits);
+        }
+
+        boolean await() throws InterruptedException {
+            return mLatch.await(5, TimeUnit.SECONDS);
+        }
+
+        @Override
+        public void onInvalidated(@NonNull Set<String> tables) {
+            mInvalidatedTables = tables;
+            mLatch.countDown();
+        }
+
+        Set<String> getInvalidatedTables() {
+            return mInvalidatedTables;
+        }
+    }
+}
diff --git a/room/integration-tests/testapp/src/androidTest/java/android/arch/persistence/room/integration/testapp/test/LiveDataQueryTest.java b/room/integration-tests/testapp/src/androidTest/java/android/arch/persistence/room/integration/testapp/test/LiveDataQueryTest.java
new file mode 100644
index 0000000..9e4be5d
--- /dev/null
+++ b/room/integration-tests/testapp/src/androidTest/java/android/arch/persistence/room/integration/testapp/test/LiveDataQueryTest.java
@@ -0,0 +1,310 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.arch.persistence.room.integration.testapp.test;
+
+import static org.hamcrest.CoreMatchers.is;
+import static org.hamcrest.CoreMatchers.notNullValue;
+import static org.hamcrest.CoreMatchers.nullValue;
+import static org.hamcrest.MatcherAssert.assertThat;
+
+import android.arch.core.executor.AppToolkitTaskExecutor;
+import android.arch.lifecycle.Lifecycle;
+import android.arch.lifecycle.LifecycleOwner;
+import android.arch.lifecycle.LifecycleRegistry;
+import android.arch.lifecycle.LiveData;
+import android.arch.lifecycle.Observer;
+import android.arch.persistence.room.InvalidationTrackerTrojan;
+import android.arch.persistence.room.integration.testapp.vo.AvgWeightByAge;
+import android.arch.persistence.room.integration.testapp.vo.Pet;
+import android.arch.persistence.room.integration.testapp.vo.User;
+import android.arch.persistence.room.integration.testapp.vo.UserAndAllPets;
+import android.support.annotation.Nullable;
+import android.support.test.InstrumentationRegistry;
+import android.support.test.filters.SmallTest;
+import android.support.test.runner.AndroidJUnit4;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.lang.ref.WeakReference;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.List;
+import java.util.UUID;
+import java.util.concurrent.Callable;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.ExecutionException;
+import java.util.concurrent.FutureTask;
+import java.util.concurrent.TimeUnit;
+
+/**
+ * Tests invalidation tracking.
+ */
+@SmallTest
+@RunWith(AndroidJUnit4.class)
+public class LiveDataQueryTest extends TestDatabaseTest {
+
+    @Test
+    public void observeById() throws InterruptedException, ExecutionException {
+        final LiveData<User> userLiveData = mUserDao.liveUserById(5);
+        final TestLifecycleOwner testOwner = new TestLifecycleOwner();
+        testOwner.handleEvent(Lifecycle.Event.ON_CREATE);
+        final LatchObserver<User> observer = new LatchObserver<>();
+        observe(userLiveData, testOwner, observer);
+
+        observer.assertNoUpdate();
+
+        observer.reset();
+        testOwner.handleEvent(Lifecycle.Event.ON_START);
+        assertThat(observer.get(), is(nullValue()));
+
+        // another id
+        observer.reset();
+        mUserDao.insert(TestUtil.createUser(7));
+        assertThat(observer.get(), is(nullValue()));
+
+        observer.reset();
+        final User u5 = TestUtil.createUser(5);
+        mUserDao.insert(u5);
+        assertThat(observer.get(), is(notNullValue()));
+
+        u5.setName("foo-foo-foo");
+        observer.reset();
+        mUserDao.insertOrReplace(u5);
+        final User updated = observer.get();
+        assertThat(updated, is(notNullValue()));
+        assertThat(updated.getName(), is("foo-foo-foo"));
+
+        testOwner.handleEvent(Lifecycle.Event.ON_STOP);
+        observer.reset();
+        u5.setName("baba");
+        mUserDao.insertOrReplace(u5);
+        observer.assertNoUpdate();
+    }
+
+    @Test
+    public void observeListQuery() throws InterruptedException, ExecutionException {
+        final LiveData<List<User>> userLiveData = mUserDao.liveUsersListByName("frida");
+        final TestLifecycleOwner lifecycleOwner = new TestLifecycleOwner();
+        lifecycleOwner.handleEvent(Lifecycle.Event.ON_START);
+        final LatchObserver<List<User>> observer = new LatchObserver<>();
+        observe(userLiveData, lifecycleOwner, observer);
+        assertThat(observer.get(), is(Collections.<User>emptyList()));
+
+        observer.reset();
+        final User user1 = TestUtil.createUser(3);
+        user1.setName("dog frida");
+        mUserDao.insert(user1);
+        assertThat(observer.get(), is(Collections.singletonList(user1)));
+
+        observer.reset();
+        final User user2 = TestUtil.createUser(5);
+        user2.setName("does not match");
+        mUserDao.insert(user2);
+        assertThat(observer.get(), is(Collections.singletonList(user1)));
+
+        observer.reset();
+        user1.setName("i don't match either");
+        mUserDao.insertOrReplace(user1);
+        assertThat(observer.get(), is(Collections.<User>emptyList()));
+
+        lifecycleOwner.handleEvent(Lifecycle.Event.ON_STOP);
+
+        observer.reset();
+        final User user3 = TestUtil.createUser(9);
+        user3.setName("painter frida");
+        mUserDao.insertOrReplace(user3);
+        observer.assertNoUpdate();
+
+        observer.reset();
+        final User user4 = TestUtil.createUser(11);
+        user4.setName("friday");
+        mUserDao.insertOrReplace(user4);
+        observer.assertNoUpdate();
+
+        lifecycleOwner.handleEvent(Lifecycle.Event.ON_START);
+        assertThat(observer.get(), is(Arrays.asList(user4, user3)));
+    }
+
+    @Test
+    public void liveDataWithPojo() throws ExecutionException, InterruptedException {
+        User[] users = TestUtil.createUsersArray(3, 5, 7, 9);
+        users[0].setAge(10);
+        users[0].setWeight(15);
+
+        users[1].setAge(20);
+        users[1].setWeight(25);
+
+        users[2].setAge(20);
+        users[2].setWeight(26);
+
+        users[3].setAge(10);
+        users[3].setWeight(21);
+
+        final TestLifecycleOwner lifecycleOwner = new TestLifecycleOwner();
+        lifecycleOwner.handleEvent(Lifecycle.Event.ON_START);
+
+        final LatchObserver<AvgWeightByAge> observer = new LatchObserver<>();
+        LiveData<AvgWeightByAge> liveData = mUserDao.maxWeightByAgeGroup();
+
+        observe(liveData, lifecycleOwner, observer);
+        assertThat(observer.get(), is(nullValue()));
+
+        observer.reset();
+        mUserDao.insertAll(users);
+        assertThat(observer.get(), is(new AvgWeightByAge(20, 25.5f)));
+
+        observer.reset();
+        User user3 = mUserDao.load(3);
+        user3.setWeight(79);
+        mUserDao.insertOrReplace(user3);
+
+        assertThat(observer.get(), is(new AvgWeightByAge(10, 50)));
+    }
+
+    @Test
+    public void withRelation() throws ExecutionException, InterruptedException {
+        final LiveData<UserAndAllPets> liveData = mUserPetDao.liveUserWithPets(3);
+        final LatchObserver<UserAndAllPets> observer = new LatchObserver<>();
+        final TestLifecycleOwner lifecycleOwner = new TestLifecycleOwner();
+        lifecycleOwner.handleEvent(Lifecycle.Event.ON_START);
+        observe(liveData, lifecycleOwner, observer);
+        assertThat(observer.get(), is(nullValue()));
+
+        observer.reset();
+        User user = TestUtil.createUser(3);
+        mUserDao.insert(user);
+        final UserAndAllPets noPets = observer.get();
+        assertThat(noPets.user, is(user));
+
+        observer.reset();
+        Pet[] pets = TestUtil.createPetsForUser(3, 1, 2);
+        mPetDao.insertAll(pets);
+
+        final UserAndAllPets withPets = observer.get();
+        assertThat(withPets.user, is(user));
+        assertThat(withPets.pets, is(Arrays.asList(pets)));
+    }
+
+    @Test
+    public void handleGc() throws ExecutionException, InterruptedException {
+        LiveData<User> liveData = mUserDao.liveUserById(3);
+        final LatchObserver<User> observer = new LatchObserver<>();
+        final TestLifecycleOwner lifecycleOwner = new TestLifecycleOwner();
+        lifecycleOwner.handleEvent(Lifecycle.Event.ON_START);
+        observe(liveData, lifecycleOwner, observer);
+        assertThat(observer.get(), is(nullValue()));
+        observer.reset();
+        final User user = TestUtil.createUser(3);
+        mUserDao.insert(user);
+        assertThat(observer.get(), is(notNullValue()));
+        observer.reset();
+        forceGc();
+        String name = UUID.randomUUID().toString();
+        mUserDao.updateById(3, name);
+        assertThat(observer.get().getName(), is(name));
+
+        // release references
+        InstrumentationRegistry.getInstrumentation().runOnMainSync(new Runnable() {
+            @Override
+            public void run() {
+                lifecycleOwner.handleEvent(Lifecycle.Event.ON_DESTROY);
+            }
+        });
+        WeakReference<LiveData> weakLiveData = new WeakReference<LiveData>(liveData);
+        //noinspection UnusedAssignment
+        liveData = null;
+        forceGc();
+        mUserDao.updateById(3, "Bar");
+        forceGc();
+        assertThat(InvalidationTrackerTrojan.countObservers(mDatabase.getInvalidationTracker()),
+                is(0));
+        assertThat(weakLiveData.get(), nullValue());
+    }
+
+    private void observe(final LiveData liveData, final LifecycleOwner provider,
+            final Observer observer) throws ExecutionException, InterruptedException {
+        FutureTask<Void> futureTask = new FutureTask<>(new Callable<Void>() {
+            @Override
+            public Void call() throws Exception {
+                //noinspection unchecked
+                liveData.observe(provider, observer);
+                return null;
+            }
+        });
+        AppToolkitTaskExecutor.getInstance().executeOnMainThread(futureTask);
+        futureTask.get();
+    }
+
+    private static void forceGc() {
+        // Use a random index in the list to detect the garbage collection each time because
+        // .get() may accidentally trigger a strong reference during collection.
+        ArrayList<WeakReference<byte[]>> leak = new ArrayList<>();
+        do {
+            WeakReference<byte[]> arr = new WeakReference<>(new byte[100]);
+            leak.add(arr);
+        } while (leak.get((int) (Math.random() * leak.size())).get() != null);
+    }
+
+    static class TestLifecycleOwner implements LifecycleOwner {
+
+        private LifecycleRegistry mLifecycle;
+
+        TestLifecycleOwner() {
+            mLifecycle = new LifecycleRegistry(this);
+        }
+
+        @Override
+        public Lifecycle getLifecycle() {
+            return mLifecycle;
+        }
+
+        void handleEvent(Lifecycle.Event event) {
+            mLifecycle.handleLifecycleEvent(event);
+        }
+    }
+
+    private class LatchObserver<T> implements Observer<T> {
+
+        static final int TIMEOUT = 3;
+
+        T mLastData;
+
+        CountDownLatch mSetLatch = new CountDownLatch(1);
+
+        void reset() {
+            mSetLatch = new CountDownLatch(1);
+        }
+
+        @Override
+        public void onChanged(@Nullable T o) {
+            mLastData = o;
+            mSetLatch.countDown();
+        }
+
+        void assertNoUpdate() throws InterruptedException {
+            assertThat(mSetLatch.await(TIMEOUT, TimeUnit.SECONDS),
+                    is(false));
+        }
+
+        T get() throws InterruptedException {
+            assertThat(mSetLatch.await(TIMEOUT, TimeUnit.SECONDS), is(true));
+            return mLastData;
+        }
+    }
+}
diff --git a/room/integration-tests/testapp/src/androidTest/java/android/arch/persistence/room/integration/testapp/test/MainThreadCheckTest.java b/room/integration-tests/testapp/src/androidTest/java/android/arch/persistence/room/integration/testapp/test/MainThreadCheckTest.java
new file mode 100644
index 0000000..f4ed5dd
--- /dev/null
+++ b/room/integration-tests/testapp/src/androidTest/java/android/arch/persistence/room/integration/testapp/test/MainThreadCheckTest.java
@@ -0,0 +1,116 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.arch.persistence.room.integration.testapp.test;
+
+import static org.hamcrest.CoreMatchers.instanceOf;
+import static org.hamcrest.CoreMatchers.notNullValue;
+import static org.hamcrest.CoreMatchers.nullValue;
+import static org.hamcrest.MatcherAssert.assertThat;
+
+import android.arch.core.util.Function;
+import android.arch.persistence.room.Room;
+import android.arch.persistence.room.RoomDatabase;
+import android.arch.persistence.room.integration.testapp.TestDatabase;
+import android.content.Context;
+import android.support.test.InstrumentationRegistry;
+import android.support.test.filters.SmallTest;
+import android.support.test.runner.AndroidJUnit4;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.util.concurrent.atomic.AtomicReference;
+
+@RunWith(AndroidJUnit4.class)
+@SmallTest
+public class MainThreadCheckTest {
+
+    @Test
+    public void testMainThread() {
+        final Throwable error = test(false, new Function<TestDatabase, Void>() {
+            @Override
+            public Void apply(TestDatabase db) {
+                db.getUserDao().load(3);
+                return null;
+            }
+        });
+        assertThat(error, notNullValue());
+        assertThat(error, instanceOf(IllegalStateException.class));
+    }
+
+    @Test
+    public void testFlowableOnMainThread() {
+        final Throwable error = test(false, new Function<TestDatabase, Void>() {
+            @Override
+            public Void apply(TestDatabase db) {
+                db.getUserDao().flowableUserById(3);
+                return null;
+            }
+        });
+        assertThat(error, nullValue());
+    }
+
+    @Test
+    public void testLiveDataOnMainThread() {
+        final Throwable error = test(false, new Function<TestDatabase, Void>() {
+            @Override
+            public Void apply(TestDatabase db) {
+                db.getUserDao().liveUserById(3);
+                return null;
+            }
+        });
+        assertThat(error, nullValue());
+    }
+
+    @Test
+    public void testAllowMainThread() {
+        final Throwable error = test(true, new Function<TestDatabase, Void>() {
+            @Override
+            public Void apply(TestDatabase db) {
+                db.getUserDao().load(3);
+                return null;
+            }
+        });
+        assertThat(error, nullValue());
+    }
+
+    private Throwable test(boolean allowMainThread, final Function<TestDatabase, Void> fun) {
+        Context context = InstrumentationRegistry.getTargetContext();
+        final RoomDatabase.Builder<TestDatabase> builder = Room.inMemoryDatabaseBuilder(
+                context, TestDatabase.class);
+        if (allowMainThread) {
+            builder.allowMainThreadQueries();
+        }
+        final TestDatabase db = builder.build();
+        final AtomicReference<Throwable> error = new AtomicReference<>();
+        try {
+            InstrumentationRegistry.getInstrumentation().runOnMainSync(new Runnable() {
+                @Override
+                public void run() {
+                    try {
+                        fun.apply(db);
+                    } catch (Throwable t) {
+                        error.set(t);
+                    }
+                }
+            });
+        } finally {
+            db.close();
+        }
+        return error.get();
+    }
+}
diff --git a/room/integration-tests/testapp/src/androidTest/java/android/arch/persistence/room/integration/testapp/test/PojoTest.java b/room/integration-tests/testapp/src/androidTest/java/android/arch/persistence/room/integration/testapp/test/PojoTest.java
new file mode 100644
index 0000000..b43e274
--- /dev/null
+++ b/room/integration-tests/testapp/src/androidTest/java/android/arch/persistence/room/integration/testapp/test/PojoTest.java
@@ -0,0 +1,73 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.arch.persistence.room.integration.testapp.test;
+
+import static org.hamcrest.CoreMatchers.is;
+import static org.hamcrest.MatcherAssert.assertThat;
+
+import android.content.Context;
+import android.support.test.InstrumentationRegistry;
+import android.support.test.runner.AndroidJUnit4;
+
+import android.arch.persistence.room.Room;
+import android.arch.persistence.room.integration.testapp.TestDatabase;
+import android.arch.persistence.room.integration.testapp.dao.UserDao;
+import android.arch.persistence.room.integration.testapp.vo.AvgWeightByAge;
+import android.arch.persistence.room.integration.testapp.vo.User;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.util.Arrays;
+
+@RunWith(AndroidJUnit4.class)
+public class PojoTest {
+    private UserDao mUserDao;
+
+    @Before
+    public void createDb() {
+        Context context = InstrumentationRegistry.getTargetContext();
+        TestDatabase db = Room.inMemoryDatabaseBuilder(context, TestDatabase.class).build();
+        mUserDao = db.getUserDao();
+    }
+
+    @Test
+    public void weightsByAge() {
+        User[] users = TestUtil.createUsersArray(3, 5, 7, 10);
+        users[0].setAge(10);
+        users[0].setWeight(20);
+
+        users[1].setAge(10);
+        users[1].setWeight(30);
+
+        users[2].setAge(15);
+        users[2].setWeight(12);
+
+        users[3].setAge(35);
+        users[3].setWeight(55);
+
+        mUserDao.insertAll(users);
+        assertThat(mUserDao.weightByAge(), is(
+                Arrays.asList(
+                        new AvgWeightByAge(35, 55),
+                        new AvgWeightByAge(10, 25),
+                        new AvgWeightByAge(15, 12)
+                )
+        ));
+    }
+}
diff --git a/room/integration-tests/testapp/src/androidTest/java/android/arch/persistence/room/integration/testapp/test/PojoWithRelationTest.java b/room/integration-tests/testapp/src/androidTest/java/android/arch/persistence/room/integration/testapp/test/PojoWithRelationTest.java
new file mode 100644
index 0000000..2057d47
--- /dev/null
+++ b/room/integration-tests/testapp/src/androidTest/java/android/arch/persistence/room/integration/testapp/test/PojoWithRelationTest.java
@@ -0,0 +1,134 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.arch.persistence.room.integration.testapp.test;
+
+import static org.hamcrest.CoreMatchers.is;
+import static org.hamcrest.MatcherAssert.assertThat;
+
+import android.support.test.filters.SmallTest;
+import android.support.test.runner.AndroidJUnit4;
+
+import android.arch.persistence.room.integration.testapp.vo.Pet;
+import android.arch.persistence.room.integration.testapp.vo.Toy;
+import android.arch.persistence.room.integration.testapp.vo.User;
+import android.arch.persistence.room.integration.testapp.vo.UserAndAllPets;
+import android.arch.persistence.room.integration.testapp.vo.UserIdAndPetNames;
+import android.arch.persistence.room.integration.testapp.vo.UserWithPetsAndToys;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.List;
+
+@RunWith(AndroidJUnit4.class)
+@SmallTest
+public class PojoWithRelationTest extends TestDatabaseTest {
+    @Test
+    public void fetchAll() {
+        User[] users = TestUtil.createUsersArray(1, 2, 3);
+        Pet[][] userPets = new Pet[3][];
+        mUserDao.insertAll(users);
+        for (User user : users) {
+            Pet[] pets = TestUtil.createPetsForUser(user.getId(), user.getId() * 10,
+                    user.getId() - 1);
+            mPetDao.insertAll(pets);
+            userPets[user.getId() - 1] = pets;
+        }
+        List<UserAndAllPets> usersAndPets = mUserPetDao.loadAllUsersWithTheirPets();
+        assertThat(usersAndPets.size(), is(3));
+        assertThat(usersAndPets.get(0).user, is(users[0]));
+        assertThat(usersAndPets.get(0).pets, is(Collections.<Pet>emptyList()));
+
+        assertThat(usersAndPets.get(1).user, is(users[1]));
+        assertThat(usersAndPets.get(1).pets, is(Arrays.asList(userPets[1])));
+
+        assertThat(usersAndPets.get(2).user, is(users[2]));
+        assertThat(usersAndPets.get(2).pets, is(Arrays.asList(userPets[2])));
+    }
+
+    private void createData() {
+        User[] users = TestUtil.createUsersArray(1, 2);
+        mUserDao.insertAll(users);
+        Pet user1_pet1 = TestUtil.createPet(1);
+        user1_pet1.setUserId(1);
+        user1_pet1.setName("pet1");
+        mPetDao.insertOrReplace(user1_pet1);
+
+        Pet user1_pet2 = TestUtil.createPet(2);
+        user1_pet2.setUserId(1);
+        user1_pet2.setName("pet2");
+        mPetDao.insertOrReplace(user1_pet2);
+
+        Pet user2_pet1 = TestUtil.createPet(3);
+        user2_pet1.setUserId(2);
+        user2_pet1.setName("pet3");
+        mPetDao.insertOrReplace(user2_pet1);
+    }
+
+    @Test
+    public void fetchWithNames() {
+        createData();
+
+        List<UserIdAndPetNames> usersAndPets = mUserPetDao.loadUserAndPetNames();
+        assertThat(usersAndPets.size(), is(2));
+        assertThat(usersAndPets.get(0).userId, is(1));
+        assertThat(usersAndPets.get(1).userId, is(2));
+        assertThat(usersAndPets.get(0).names, is(Arrays.asList("pet1", "pet2")));
+        assertThat(usersAndPets.get(1).names, is(Collections.singletonList("pet3")));
+    }
+
+    @Test
+    public void nested() {
+        createData();
+        Toy pet1_toy1 = new Toy();
+        pet1_toy1.setName("toy1");
+        pet1_toy1.setPetId(1);
+        Toy pet1_toy2 = new Toy();
+        pet1_toy2.setName("toy2");
+        pet1_toy2.setPetId(1);
+        mToyDao.insert(pet1_toy1, pet1_toy2);
+        List<UserWithPetsAndToys> userWithPetsAndToys = mUserPetDao.loadUserWithPetsAndToys();
+        assertThat(userWithPetsAndToys.size(), is(2));
+        UserWithPetsAndToys first = userWithPetsAndToys.get(0);
+        List<Toy> toys = first.pets.get(0).toys;
+        assertThat(toys.get(0).getName(), is("toy1"));
+        assertThat(toys.get(1).getName(), is("toy2"));
+        assertThat(userWithPetsAndToys.get(1).pets.get(0).toys, is(Collections.<Toy>emptyList()));
+    }
+
+    @Test
+    public void duplicateParentField() {
+        User[] users = TestUtil.createUsersArray(1, 2);
+        Pet[] pets_1 = TestUtil.createPetsForUser(1, 1, 2);
+        Pet[] pets_2 = TestUtil.createPetsForUser(2, 10, 1);
+        mUserDao.insertAll(users);
+        mPetDao.insertAll(pets_1);
+        mPetDao.insertAll(pets_2);
+        List<UserAndAllPets> userAndAllPets = mUserPetDao.unionByItself();
+        assertThat(userAndAllPets.size(), is(4));
+        for (int i = 0; i < 4; i++) {
+            assertThat("user at " + i, userAndAllPets.get(i).user, is(users[i % 2]));
+        }
+        assertThat(userAndAllPets.get(0).pets, is(Arrays.asList(pets_1)));
+        assertThat(userAndAllPets.get(2).pets, is(Arrays.asList(pets_1)));
+
+        assertThat(userAndAllPets.get(1).pets, is(Arrays.asList(pets_2)));
+        assertThat(userAndAllPets.get(3).pets, is(Arrays.asList(pets_2)));
+    }
+}
diff --git a/room/integration-tests/testapp/src/androidTest/java/android/arch/persistence/room/integration/testapp/test/PrimaryKeyTest.java b/room/integration-tests/testapp/src/androidTest/java/android/arch/persistence/room/integration/testapp/test/PrimaryKeyTest.java
new file mode 100644
index 0000000..97ce10c
--- /dev/null
+++ b/room/integration-tests/testapp/src/androidTest/java/android/arch/persistence/room/integration/testapp/test/PrimaryKeyTest.java
@@ -0,0 +1,114 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.arch.persistence.room.integration.testapp.test;
+
+import static org.hamcrest.CoreMatchers.is;
+import static org.hamcrest.CoreMatchers.notNullValue;
+import static org.hamcrest.MatcherAssert.assertThat;
+
+import android.support.test.InstrumentationRegistry;
+import android.support.test.filters.SmallTest;
+import android.support.test.runner.AndroidJUnit4;
+
+import android.arch.persistence.room.Room;
+import android.arch.persistence.room.integration.testapp.PKeyTestDatabase;
+import android.arch.persistence.room.integration.testapp.vo.IntAutoIncPKeyEntity;
+import android.arch.persistence.room.integration.testapp.vo.IntegerAutoIncPKeyEntity;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.util.Arrays;
+
+@RunWith(AndroidJUnit4.class)
+@SmallTest
+public class PrimaryKeyTest {
+    private PKeyTestDatabase mDatabase;
+    @Before
+    public void setup() {
+        mDatabase = Room.inMemoryDatabaseBuilder(InstrumentationRegistry.getTargetContext(),
+                PKeyTestDatabase.class).build();
+    }
+
+    @Test
+    public void integerTest() {
+        IntegerAutoIncPKeyEntity entity = new IntegerAutoIncPKeyEntity();
+        entity.data = "foo";
+        mDatabase.integerPKeyDao().insertMe(entity);
+        IntegerAutoIncPKeyEntity loaded = mDatabase.integerPKeyDao().getMe(1);
+        assertThat(loaded, notNullValue());
+        assertThat(loaded.data, is(entity.data));
+    }
+
+    @Test
+    public void dontOverrideNullable0() {
+        IntegerAutoIncPKeyEntity entity = new IntegerAutoIncPKeyEntity();
+        entity.pKey = 0;
+        entity.data = "foo";
+        mDatabase.integerPKeyDao().insertMe(entity);
+        IntegerAutoIncPKeyEntity loaded = mDatabase.integerPKeyDao().getMe(0);
+        assertThat(loaded, notNullValue());
+        assertThat(loaded.data, is(entity.data));
+    }
+
+    @Test
+    public void intTest() {
+        IntAutoIncPKeyEntity entity = new IntAutoIncPKeyEntity();
+        entity.data = "foo";
+        mDatabase.intPKeyDao().insertMe(entity);
+        IntAutoIncPKeyEntity loaded = mDatabase.intPKeyDao().getMe(1);
+        assertThat(loaded, notNullValue());
+        assertThat(loaded.data, is(entity.data));
+    }
+
+    @Test
+    public void getInsertedId() {
+        IntAutoIncPKeyEntity entity = new IntAutoIncPKeyEntity();
+        entity.data = "foo";
+        final long id = mDatabase.intPKeyDao().insertAndGetId(entity);
+        assertThat(mDatabase.intPKeyDao().getMe((int) id).data, is("foo"));
+    }
+
+    @Test
+    public void getInsertedIds() {
+        IntAutoIncPKeyEntity entity = new IntAutoIncPKeyEntity();
+        entity.data = "foo";
+        IntAutoIncPKeyEntity entity2 = new IntAutoIncPKeyEntity();
+        entity2.data = "foo2";
+        final long[] ids = mDatabase.intPKeyDao().insertAndGetIds(entity, entity2);
+        assertThat(mDatabase.intPKeyDao().loadDataById(ids), is(Arrays.asList("foo", "foo2")));
+    }
+
+    @Test
+    public void getInsertedIdFromInteger() {
+        IntegerAutoIncPKeyEntity entity = new IntegerAutoIncPKeyEntity();
+        entity.data = "foo";
+        final long id = mDatabase.integerPKeyDao().insertAndGetId(entity);
+        assertThat(mDatabase.integerPKeyDao().getMe((int) id).data, is("foo"));
+    }
+
+    @Test
+    public void getInsertedIdsFromInteger() {
+        IntegerAutoIncPKeyEntity entity = new IntegerAutoIncPKeyEntity();
+        entity.data = "foo";
+        IntegerAutoIncPKeyEntity entity2 = new IntegerAutoIncPKeyEntity();
+        entity2.data = "foo2";
+        final long[] ids = mDatabase.integerPKeyDao().insertAndGetIds(entity, entity2);
+        assertThat(mDatabase.integerPKeyDao().loadDataById(ids), is(Arrays.asList("foo", "foo2")));
+    }
+}
diff --git a/room/integration-tests/testapp/src/androidTest/java/android/arch/persistence/room/integration/testapp/test/RxJava2Test.java b/room/integration-tests/testapp/src/androidTest/java/android/arch/persistence/room/integration/testapp/test/RxJava2Test.java
new file mode 100644
index 0000000..9d696ab
--- /dev/null
+++ b/room/integration-tests/testapp/src/androidTest/java/android/arch/persistence/room/integration/testapp/test/RxJava2Test.java
@@ -0,0 +1,166 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.arch.persistence.room.integration.testapp.test;
+
+import static org.hamcrest.CoreMatchers.is;
+import static org.hamcrest.MatcherAssert.assertThat;
+
+import android.support.test.filters.MediumTest;
+import android.support.test.filters.SmallTest;
+import android.support.test.runner.AndroidJUnit4;
+
+import android.arch.core.executor.AppToolkitTaskExecutor;
+import android.arch.core.executor.TaskExecutor;
+import android.arch.persistence.room.integration.testapp.vo.User;
+
+import org.junit.After;
+import org.junit.Assert;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import io.reactivex.disposables.Disposable;
+import io.reactivex.schedulers.TestScheduler;
+import io.reactivex.subscribers.TestSubscriber;
+
+@SmallTest
+@RunWith(AndroidJUnit4.class)
+public class RxJava2Test extends TestDatabaseTest {
+
+    private TestScheduler mTestScheduler;
+
+    @Before
+    public void setupSchedulers() {
+        mTestScheduler = new TestScheduler();
+        mTestScheduler.start();
+        AppToolkitTaskExecutor.getInstance().setDelegate(new TaskExecutor() {
+            @Override
+            public void executeOnDiskIO(Runnable runnable) {
+                mTestScheduler.scheduleDirect(runnable);
+            }
+
+            @Override
+            public void postToMainThread(Runnable runnable) {
+                Assert.fail("no main thread in this test");
+            }
+
+            @Override
+            public boolean isMainThread() {
+                return false;
+            }
+        });
+    }
+
+    @After
+    public void clearSchedulers() {
+        mTestScheduler.shutdown();
+        AppToolkitTaskExecutor.getInstance().setDelegate(null);
+    }
+
+    private void drain() throws InterruptedException {
+        mTestScheduler.triggerActions();
+    }
+
+    @Test
+    public void observeOnce() throws InterruptedException {
+        User user = TestUtil.createUser(3);
+        mUserDao.insert(user);
+        drain();
+        TestSubscriber<User> consumer = new TestSubscriber<>();
+        Disposable disposable = mUserDao.flowableUserById(3).subscribeWith(consumer);
+        drain();
+        consumer.assertValue(user);
+        disposable.dispose();
+    }
+
+    @Test
+    public void observeChangeAndDispose() throws InterruptedException {
+        User user = TestUtil.createUser(3);
+        mUserDao.insert(user);
+        drain();
+        TestSubscriber<User> consumer = new TestSubscriber<>();
+        Disposable disposable = mUserDao.flowableUserById(3).observeOn(mTestScheduler)
+                .subscribeWith(consumer);
+        drain();
+        assertThat(consumer.values().get(0), is(user));
+        user.setName("rxy");
+        mUserDao.insertOrReplace(user);
+        drain();
+        User next = consumer.values().get(1);
+        assertThat(next, is(user));
+        disposable.dispose();
+        user.setName("foo");
+        mUserDao.insertOrReplace(user);
+        drain();
+        assertThat(consumer.valueCount(), is(2));
+    }
+
+    @Test
+    @MediumTest
+    public void observeEmpty() throws InterruptedException {
+        TestSubscriber<User> consumer = new TestSubscriber<>();
+        Disposable disposable = mUserDao.flowableUserById(3).observeOn(mTestScheduler)
+                .subscribeWith(consumer);
+        drain();
+        consumer.assertNoValues();
+        User user = TestUtil.createUser(3);
+        mUserDao.insert(user);
+        drain();
+        assertThat(consumer.values().get(0), is(user));
+        disposable.dispose();
+        user.setAge(88);
+        mUserDao.insertOrReplace(user);
+        drain();
+        assertThat(consumer.valueCount(), is(1));
+    }
+
+    @Test
+    public void flowableCountUsers() throws InterruptedException {
+        TestSubscriber<Integer> consumer = new TestSubscriber<>();
+        mUserDao.flowableCountUsers()
+                .observeOn(mTestScheduler)
+                .subscribe(consumer);
+        drain();
+        assertThat(consumer.values().get(0), is(0));
+        mUserDao.insertAll(TestUtil.createUsersArray(1, 3, 4, 6));
+        drain();
+        assertThat(consumer.values().get(1), is(4));
+        mUserDao.deleteByUids(3, 7);
+        drain();
+        assertThat(consumer.values().get(2), is(3));
+        mUserDao.deleteByUids(101);
+        drain();
+        assertThat(consumer.valueCount(), is(3));
+    }
+
+    @Test
+    @MediumTest
+    public void publisherCountUsers() throws InterruptedException {
+        TestSubscriber<Integer> subscriber = new TestSubscriber<>();
+        mUserDao.publisherCountUsers().subscribe(subscriber);
+        drain();
+        subscriber.assertSubscribed();
+        subscriber.request(2);
+        drain();
+        subscriber.assertValue(0);
+        mUserDao.insert(TestUtil.createUser(2));
+        drain();
+        subscriber.assertValues(0, 1);
+        subscriber.cancel();
+        subscriber.assertNoErrors();
+    }
+}
diff --git a/room/integration-tests/testapp/src/androidTest/java/android/arch/persistence/room/integration/testapp/test/SimpleEntityReadWriteTest.java b/room/integration-tests/testapp/src/androidTest/java/android/arch/persistence/room/integration/testapp/test/SimpleEntityReadWriteTest.java
new file mode 100644
index 0000000..fe363a2
--- /dev/null
+++ b/room/integration-tests/testapp/src/androidTest/java/android/arch/persistence/room/integration/testapp/test/SimpleEntityReadWriteTest.java
@@ -0,0 +1,428 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.arch.persistence.room.integration.testapp.test;
+
+import static org.hamcrest.CoreMatchers.equalTo;
+import static org.hamcrest.CoreMatchers.is;
+import static org.hamcrest.CoreMatchers.not;
+import static org.hamcrest.CoreMatchers.nullValue;
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.hamcrest.Matchers.hasSize;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
+
+import android.arch.persistence.room.Room;
+import android.arch.persistence.room.integration.testapp.TestDatabase;
+import android.arch.persistence.room.integration.testapp.dao.BlobEntityDao;
+import android.arch.persistence.room.integration.testapp.dao.PetDao;
+import android.arch.persistence.room.integration.testapp.dao.UserDao;
+import android.arch.persistence.room.integration.testapp.dao.UserPetDao;
+import android.arch.persistence.room.integration.testapp.vo.BlobEntity;
+import android.arch.persistence.room.integration.testapp.vo.Pet;
+import android.arch.persistence.room.integration.testapp.vo.User;
+import android.arch.persistence.room.integration.testapp.vo.UserAndAllPets;
+import android.content.Context;
+import android.database.Cursor;
+import android.database.sqlite.SQLiteConstraintException;
+import android.database.sqlite.SQLiteException;
+import android.support.test.InstrumentationRegistry;
+import android.support.test.filters.SmallTest;
+import android.support.test.runner.AndroidJUnit4;
+
+import junit.framework.AssertionFailedError;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.util.Arrays;
+import java.util.Calendar;
+import java.util.Collections;
+import java.util.Date;
+import java.util.List;
+
+@SuppressWarnings("ArraysAsListWithZeroOrOneArgument")
+@SmallTest
+@RunWith(AndroidJUnit4.class)
+public class SimpleEntityReadWriteTest {
+    private UserDao mUserDao;
+    private BlobEntityDao mBlobEntityDao;
+    private PetDao mPetDao;
+    private UserPetDao mUserPetDao;
+
+    @Before
+    public void createDb() {
+        Context context = InstrumentationRegistry.getTargetContext();
+        TestDatabase db = Room.inMemoryDatabaseBuilder(context, TestDatabase.class).build();
+        mUserDao = db.getUserDao();
+        mPetDao = db.getPetDao();
+        mUserPetDao = db.getUserPetDao();
+        mBlobEntityDao = db.getBlobEntityDao();
+    }
+
+    @Test
+    public void writeUserAndReadInList() throws Exception {
+        User user = TestUtil.createUser(3);
+        user.setName("george");
+        mUserDao.insert(user);
+        List<User> byName = mUserDao.findUsersByName("george");
+        assertThat(byName.get(0), equalTo(user));
+    }
+
+    @Test
+    public void insertDifferentEntities() throws Exception {
+        User user1 = TestUtil.createUser(3);
+        user1.setName("george");
+        Pet pet = TestUtil.createPet(1);
+        pet.setUserId(3);
+        pet.setName("a");
+        mUserPetDao.insertUserAndPet(user1, pet);
+        assertThat(mUserDao.count(), is(1));
+        List<UserAndAllPets> inserted = mUserPetDao.loadAllUsersWithTheirPets();
+        assertThat(inserted, hasSize(1));
+        assertThat(inserted.get(0).user.getId(), is(3));
+        assertThat(inserted.get(0).user.getName(), is(equalTo("george")));
+        assertThat(inserted.get(0).pets, hasSize(1));
+        assertThat(inserted.get(0).pets.get(0).getPetId(), is(1));
+        assertThat(inserted.get(0).pets.get(0).getName(), is("a"));
+        assertThat(inserted.get(0).pets.get(0).getUserId(), is(3));
+        pet.setName("b");
+        mUserPetDao.updateUsersAndPets(new User[]{user1}, new Pet[]{pet});
+        List<UserAndAllPets> updated = mUserPetDao.loadAllUsersWithTheirPets();
+        assertThat(updated, hasSize(1));
+        assertThat(updated.get(0).pets, hasSize(1));
+        assertThat(updated.get(0).pets.get(0).getName(), is("b"));
+        User user2 = TestUtil.createUser(5);
+        user2.setName("chet");
+        mUserDao.insert(user2);
+        assertThat(mUserDao.count(), is(2));
+        mUserPetDao.delete2UsersAndPets(user1, user2, new Pet[]{pet});
+        List<UserAndAllPets> deleted = mUserPetDao.loadAllUsersWithTheirPets();
+        assertThat(deleted, hasSize(0));
+    }
+
+    @Test
+    public void insertDifferentEntities_transaction() throws Exception {
+        Pet pet = TestUtil.createPet(1);
+        mPetDao.insertOrReplace(pet);
+        assertThat(mPetDao.count(), is(1));
+        User user = TestUtil.createUser(3);
+        try {
+            mUserPetDao.insertUserAndPet(user, pet);
+            fail("Exception expected");
+        } catch (SQLiteConstraintException ignored) {
+        }
+        assertThat(mUserDao.count(), is(0));
+        assertThat(mPetDao.count(), is(1));
+    }
+
+    @Test
+    public void throwExceptionOnConflict() {
+        User user = TestUtil.createUser(3);
+        mUserDao.insert(user);
+
+        User user2 = TestUtil.createUser(3);
+        try {
+            mUserDao.insert(user2);
+            throw new AssertionFailedError("didn't throw in conflicting insertion");
+        } catch (SQLiteException ignored) {
+        }
+    }
+
+    @Test
+    public void replaceOnConflict() {
+        User user = TestUtil.createUser(3);
+        mUserDao.insert(user);
+
+        User user2 = TestUtil.createUser(3);
+        mUserDao.insertOrReplace(user2);
+
+        assertThat(mUserDao.load(3), equalTo(user2));
+        assertThat(mUserDao.load(3), not(equalTo(user)));
+    }
+
+    @Test
+    public void updateSimple() {
+        User user = TestUtil.createUser(3);
+        mUserDao.insert(user);
+        user.setName("i am an updated name");
+        assertThat(mUserDao.update(user), is(1));
+        assertThat(mUserDao.load(user.getId()), equalTo(user));
+    }
+
+    @Test
+    public void updateNonExisting() {
+        User user = TestUtil.createUser(3);
+        mUserDao.insert(user);
+        User user2 = TestUtil.createUser(4);
+        assertThat(mUserDao.update(user2), is(0));
+    }
+
+    @Test
+    public void updateList() {
+        List<User> users = TestUtil.createUsersList(3, 4, 5);
+        mUserDao.insertAll(users.toArray(new User[3]));
+        for (User user : users) {
+            user.setName("name " + user.getId());
+        }
+        assertThat(mUserDao.updateAll(users), is(3));
+        for (User user : users) {
+            assertThat(mUserDao.load(user.getId()).getName(), is("name " + user.getId()));
+        }
+    }
+
+    @Test
+    public void updateListPartial() {
+        List<User> existingUsers = TestUtil.createUsersList(3, 4, 5);
+        mUserDao.insertAll(existingUsers.toArray(new User[3]));
+        for (User user : existingUsers) {
+            user.setName("name " + user.getId());
+        }
+        List<User> allUsers = TestUtil.createUsersList(7, 8, 9);
+        allUsers.addAll(existingUsers);
+        assertThat(mUserDao.updateAll(allUsers), is(3));
+        for (User user : existingUsers) {
+            assertThat(mUserDao.load(user.getId()).getName(), is("name " + user.getId()));
+        }
+    }
+
+    @Test
+    public void delete() {
+        User user = TestUtil.createUser(3);
+        mUserDao.insert(user);
+        assertThat(mUserDao.delete(user), is(1));
+        assertThat(mUserDao.delete(user), is(0));
+        assertThat(mUserDao.load(3), is(nullValue()));
+    }
+
+    @Test
+    public void deleteAll() {
+        User[] users = TestUtil.createUsersArray(3, 5, 7, 9);
+        mUserDao.insertAll(users);
+        // there is actually no guarantee for this order by works fine since they are ordered for
+        // the test and it is a new database (no pages to recycle etc)
+        assertThat(mUserDao.loadByIds(3, 5, 7, 9), is(users));
+        int deleteCount = mUserDao.deleteAll(new User[]{users[0], users[3],
+                TestUtil.createUser(9)});
+        assertThat(deleteCount, is(2));
+        assertThat(mUserDao.loadByIds(3, 5, 7, 9), is(new User[]{users[1], users[2]}));
+    }
+
+    @Test
+    public void findByBoolean() {
+        User user1 = TestUtil.createUser(3);
+        user1.setAdmin(true);
+        User user2 = TestUtil.createUser(5);
+        user2.setAdmin(false);
+        mUserDao.insert(user1);
+        mUserDao.insert(user2);
+        assertThat(mUserDao.findByAdmin(true), is(Arrays.asList(user1)));
+        assertThat(mUserDao.findByAdmin(false), is(Arrays.asList(user2)));
+    }
+
+    @Test
+    public void deleteByAge() {
+        User user1 = TestUtil.createUser(3);
+        user1.setAge(30);
+        User user2 = TestUtil.createUser(5);
+        user2.setAge(45);
+        mUserDao.insert(user1);
+        mUserDao.insert(user2);
+        assertThat(mUserDao.deleteAgeGreaterThan(60), is(0));
+        assertThat(mUserDao.deleteAgeGreaterThan(45), is(0));
+        assertThat(mUserDao.deleteAgeGreaterThan(35), is(1));
+        assertThat(mUserDao.loadByIds(3, 5), is(new User[]{user1}));
+    }
+
+    @Test
+    public void deleteByAgeRange() {
+        User user1 = TestUtil.createUser(3);
+        user1.setAge(30);
+        User user2 = TestUtil.createUser(5);
+        user2.setAge(45);
+        mUserDao.insert(user1);
+        mUserDao.insert(user2);
+        assertThat(mUserDao.deleteByAgeRange(35, 40), is(0));
+        assertThat(mUserDao.deleteByAgeRange(25, 30), is(1));
+        assertThat(mUserDao.loadByIds(3, 5), is(new User[]{user2}));
+    }
+
+    @Test
+    public void deleteByUIds() {
+        User[] users = TestUtil.createUsersArray(3, 5, 7, 9, 11);
+        mUserDao.insertAll(users);
+        assertThat(mUserDao.deleteByUids(2, 4, 6), is(0));
+        assertThat(mUserDao.deleteByUids(3, 11), is(2));
+        assertThat(mUserDao.loadByIds(3, 5, 7, 9, 11), is(new User[]{
+                users[1], users[2], users[3]
+        }));
+    }
+
+    @Test
+    public void updateNameById() {
+        User[] usersArray = TestUtil.createUsersArray(3, 5, 7);
+        mUserDao.insertAll(usersArray);
+        assertThat("test sanity", usersArray[1].getName(), not(equalTo("updated name")));
+        int changed = mUserDao.updateById(5, "updated name");
+        assertThat(changed, is(1));
+        assertThat(mUserDao.load(5).getName(), is("updated name"));
+    }
+
+    @Test
+    public void incrementIds() {
+        User[] usersArr = TestUtil.createUsersArray(2, 4, 6);
+        mUserDao.insertAll(usersArr);
+        mUserDao.incrementIds(1);
+        assertThat(mUserDao.loadIds(), is(Arrays.asList(3, 5, 7)));
+    }
+
+    @Test
+    public void findByIntQueryParameter() {
+        User user = TestUtil.createUser(1);
+        final String name = "my name";
+        user.setName(name);
+        mUserDao.insert(user);
+        assertThat(mUserDao.findByNameLength(name.length()), is(Collections.singletonList(user)));
+    }
+
+    @Test
+    public void findByIntFieldMatch() {
+        User user = TestUtil.createUser(1);
+        user.setAge(19);
+        mUserDao.insert(user);
+        assertThat(mUserDao.findByAge(19), is(Collections.singletonList(user)));
+    }
+
+    @Test
+    public void customConverterField() {
+        User user = TestUtil.createUser(20);
+        Date theDate = new Date(System.currentTimeMillis() - 200);
+        user.setBirthday(theDate);
+        mUserDao.insert(user);
+        assertThat(mUserDao.findByBirthdayRange(new Date(theDate.getTime() - 100),
+                new Date(theDate.getTime() + 1)).get(0), is(user));
+        assertThat(mUserDao.findByBirthdayRange(new Date(theDate.getTime()),
+                new Date(theDate.getTime() + 1)).size(), is(0));
+    }
+
+    @Test
+    public void renamedField() {
+        User user = TestUtil.createUser(3);
+        user.setCustomField("foo laaa");
+        mUserDao.insertOrReplace(user);
+        User loaded = mUserDao.load(3);
+        assertThat(loaded.getCustomField(), is("foo laaa"));
+        assertThat(loaded, is(user));
+    }
+
+    @Test
+    public void readViaCursor() {
+        User[] users = TestUtil.createUsersArray(3, 5, 7, 9);
+        mUserDao.insertAll(users);
+        Cursor cursor = mUserDao.findUsersAsCursor(3, 5, 9);
+        try {
+            assertThat(cursor.getCount(), is(3));
+            assertThat(cursor.moveToNext(), is(true));
+            assertThat(cursor.getInt(0), is(3));
+            assertThat(cursor.moveToNext(), is(true));
+            assertThat(cursor.getInt(0), is(5));
+            assertThat(cursor.moveToNext(), is(true));
+            assertThat(cursor.getInt(0), is(9));
+            assertThat(cursor.moveToNext(), is(false));
+        } finally {
+            cursor.close();
+        }
+    }
+
+    @Test
+    public void readDirectWithTypeAdapter() {
+        User user = TestUtil.createUser(3);
+        user.setBirthday(null);
+        mUserDao.insert(user);
+        assertThat(mUserDao.getBirthday(3), is(nullValue()));
+        Calendar calendar = Calendar.getInstance();
+        calendar.add(Calendar.YEAR, 3);
+        Date birthday = calendar.getTime();
+        user.setBirthday(birthday);
+
+        mUserDao.update(user);
+        assertThat(mUserDao.getBirthday(3), is(birthday));
+    }
+
+    @Test
+    public void emptyInQuery() {
+        User[] users = mUserDao.loadByIds();
+        assertThat(users, is(new User[0]));
+    }
+
+    @Test
+    public void blob() {
+        BlobEntity a = new BlobEntity(1, "abc".getBytes());
+        BlobEntity b = new BlobEntity(2, "def".getBytes());
+        mBlobEntityDao.insert(a);
+        mBlobEntityDao.insert(b);
+        List<BlobEntity> list = mBlobEntityDao.selectAll();
+        assertThat(list, hasSize(2));
+        mBlobEntityDao.updateContent(2, "ghi".getBytes());
+        assertThat(mBlobEntityDao.getContent(2), is(equalTo("ghi".getBytes())));
+    }
+
+    @Test
+    public void transactionByRunnable() {
+        User a = TestUtil.createUser(3);
+        User b = TestUtil.createUser(5);
+        mUserDao.insertBothByRunnable(a, b);
+        assertThat(mUserDao.count(), is(2));
+    }
+
+    @Test
+    public void transactionByRunnable_failure() {
+        User a = TestUtil.createUser(3);
+        User b = TestUtil.createUser(3);
+        boolean caught = false;
+        try {
+            mUserDao.insertBothByRunnable(a, b);
+        } catch (SQLiteConstraintException e) {
+            caught = true;
+        }
+        assertTrue("SQLiteConstraintException expected", caught);
+        assertThat(mUserDao.count(), is(0));
+    }
+
+    @Test
+    public void transactionByCallable() {
+        User a = TestUtil.createUser(3);
+        User b = TestUtil.createUser(5);
+        int count = mUserDao.insertBothByCallable(a, b);
+        assertThat(mUserDao.count(), is(2));
+        assertThat(count, is(2));
+    }
+
+    @Test
+    public void transactionByCallable_failure() {
+        User a = TestUtil.createUser(3);
+        User b = TestUtil.createUser(3);
+        boolean caught = false;
+        try {
+            mUserDao.insertBothByCallable(a, b);
+        } catch (SQLiteConstraintException e) {
+            caught = true;
+        }
+        assertTrue("SQLiteConstraintException expected", caught);
+        assertThat(mUserDao.count(), is(0));
+    }
+}
diff --git a/room/integration-tests/testapp/src/androidTest/java/android/arch/persistence/room/integration/testapp/test/TestDatabaseTest.java b/room/integration-tests/testapp/src/androidTest/java/android/arch/persistence/room/integration/testapp/test/TestDatabaseTest.java
new file mode 100644
index 0000000..02499a7
--- /dev/null
+++ b/room/integration-tests/testapp/src/androidTest/java/android/arch/persistence/room/integration/testapp/test/TestDatabaseTest.java
@@ -0,0 +1,54 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.arch.persistence.room.integration.testapp.test;
+
+import android.content.Context;
+import android.support.test.InstrumentationRegistry;
+
+import android.arch.persistence.room.Room;
+import android.arch.persistence.room.integration.testapp.TestDatabase;
+import android.arch.persistence.room.integration.testapp.dao.PetCoupleDao;
+import android.arch.persistence.room.integration.testapp.dao.PetDao;
+import android.arch.persistence.room.integration.testapp.dao.SchoolDao;
+import android.arch.persistence.room.integration.testapp.dao.ToyDao;
+import android.arch.persistence.room.integration.testapp.dao.UserDao;
+import android.arch.persistence.room.integration.testapp.dao.UserPetDao;
+
+import org.junit.Before;
+
+@SuppressWarnings("WeakerAccess")
+public abstract class TestDatabaseTest {
+    protected TestDatabase mDatabase;
+    protected UserDao mUserDao;
+    protected PetDao mPetDao;
+    protected UserPetDao mUserPetDao;
+    protected SchoolDao mSchoolDao;
+    protected PetCoupleDao mPetCoupleDao;
+    protected ToyDao mToyDao;
+
+    @Before
+    public void createDb() {
+        Context context = InstrumentationRegistry.getTargetContext();
+        mDatabase = Room.inMemoryDatabaseBuilder(context, TestDatabase.class).build();
+        mUserDao = mDatabase.getUserDao();
+        mPetDao = mDatabase.getPetDao();
+        mUserPetDao = mDatabase.getUserPetDao();
+        mSchoolDao = mDatabase.getSchoolDao();
+        mPetCoupleDao = mDatabase.getPetCoupleDao();
+        mToyDao = mDatabase.getToyDao();
+    }
+}
diff --git a/room/integration-tests/testapp/src/androidTest/java/android/arch/persistence/room/integration/testapp/test/TestUtil.java b/room/integration-tests/testapp/src/androidTest/java/android/arch/persistence/room/integration/testapp/test/TestUtil.java
new file mode 100644
index 0000000..0a35b2f
--- /dev/null
+++ b/room/integration-tests/testapp/src/androidTest/java/android/arch/persistence/room/integration/testapp/test/TestUtil.java
@@ -0,0 +1,99 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.arch.persistence.room.integration.testapp.test;
+
+import android.arch.persistence.room.integration.testapp.vo.Address;
+import android.arch.persistence.room.integration.testapp.vo.Coordinates;
+import android.arch.persistence.room.integration.testapp.vo.Pet;
+import android.arch.persistence.room.integration.testapp.vo.School;
+import android.arch.persistence.room.integration.testapp.vo.User;
+
+import java.util.ArrayList;
+import java.util.Date;
+import java.util.List;
+import java.util.UUID;
+
+public class TestUtil {
+    public static User[] createUsersArray(int... ids) {
+        User[] result = new User[ids.length];
+        for (int i = 0; i < ids.length; i++) {
+            result[i] = createUser(ids[i]);
+        }
+        return result;
+    }
+
+    public static List<User> createUsersList(int... ids) {
+        List<User> result = new ArrayList<>();
+        for (int id : ids) {
+            result.add(createUser(id));
+        }
+        return result;
+    }
+
+    public static User createUser(int id) {
+        User user = new User();
+        user.setId(id);
+        user.setName(UUID.randomUUID().toString());
+        user.setLastName(UUID.randomUUID().toString());
+        user.setAge((int) (10 + Math.random() * 50));
+        user.setCustomField(UUID.randomUUID().toString());
+        user.setBirthday(new Date());
+        return user;
+    }
+
+    public static Pet createPet(int id) {
+        Pet pet = new Pet();
+        pet.setPetId(id);
+        pet.setName(UUID.randomUUID().toString());
+        return pet;
+    }
+
+    public static Pet[] createPetsForUser(int uid, int petStartId, int count) {
+        Pet[] pets = new Pet[count];
+        for (int i = 0; i < count; i++) {
+            Pet pet = createPet(petStartId++);
+            pet.setUserId(uid);
+            pets[i] = pet;
+        }
+        return pets;
+    }
+
+    public static School createSchool(int id, int managerId) {
+        School school = new School();
+        school.setId(id);
+        school.setName(UUID.randomUUID().toString());
+        school.setManager(createUser(managerId));
+        school.setAddress(createAddress());
+        return school;
+    }
+
+    private static Address createAddress() {
+        Address address = new Address();
+        address.setCoordinates(createCoordinates());
+        address.setPostCode((int) (Math.random() * 1000 + 1000));
+        address.setState(UUID.randomUUID().toString().substring(0, 2));
+        address.setStreet(UUID.randomUUID().toString());
+        return address;
+    }
+
+    private static Coordinates createCoordinates() {
+        Coordinates coordinates = new Coordinates();
+        coordinates.lat = Math.random();
+        coordinates.lng = Math.random();
+        return coordinates;
+    }
+}
diff --git a/room/integration-tests/testapp/src/androidTest/java/android/arch/persistence/room/integration/testapp/vo/Address.java b/room/integration-tests/testapp/src/androidTest/java/android/arch/persistence/room/integration/testapp/vo/Address.java
new file mode 100644
index 0000000..46f3bb6
--- /dev/null
+++ b/room/integration-tests/testapp/src/androidTest/java/android/arch/persistence/room/integration/testapp/vo/Address.java
@@ -0,0 +1,90 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.arch.persistence.room.integration.testapp.vo;
+
+import android.arch.persistence.room.ColumnInfo;
+import android.arch.persistence.room.Embedded;
+
+public class Address {
+    @ColumnInfo(name = "street")
+    private String mStreet;
+    @ColumnInfo(name = "state")
+    private String mState;
+    @ColumnInfo(name = "post_code")
+    private int mPostCode;
+    @Embedded
+    private Coordinates mCoordinates;
+
+    public String getStreet() {
+        return mStreet;
+    }
+
+    public void setStreet(String street) {
+        mStreet = street;
+    }
+
+    public String getState() {
+        return mState;
+    }
+
+    public void setState(String state) {
+        mState = state;
+    }
+
+    public int getPostCode() {
+        return mPostCode;
+    }
+
+    public void setPostCode(int postCode) {
+        mPostCode = postCode;
+    }
+
+    public Coordinates getCoordinates() {
+        return mCoordinates;
+    }
+
+    public void setCoordinates(Coordinates coordinates) {
+        mCoordinates = coordinates;
+    }
+
+    @Override
+    public boolean equals(Object o) {
+        if (this == o) return true;
+        if (o == null || getClass() != o.getClass()) return false;
+
+        Address address = (Address) o;
+
+        if (mPostCode != address.mPostCode) return false;
+        if (mStreet != null ? !mStreet.equals(address.mStreet) : address.mStreet != null) {
+            return false;
+        }
+        if (mState != null ? !mState.equals(address.mState) : address.mState != null) {
+            return false;
+        }
+        return mCoordinates != null ? mCoordinates.equals(address.mCoordinates)
+                : address.mCoordinates == null;
+    }
+
+    @Override
+    public int hashCode() {
+        int result = mStreet != null ? mStreet.hashCode() : 0;
+        result = 31 * result + (mState != null ? mState.hashCode() : 0);
+        result = 31 * result + mPostCode;
+        result = 31 * result + (mCoordinates != null ? mCoordinates.hashCode() : 0);
+        return result;
+    }
+}
diff --git a/room/integration-tests/testapp/src/androidTest/java/android/arch/persistence/room/integration/testapp/vo/AvgWeightByAge.java b/room/integration-tests/testapp/src/androidTest/java/android/arch/persistence/room/integration/testapp/vo/AvgWeightByAge.java
new file mode 100644
index 0000000..4d22f13
--- /dev/null
+++ b/room/integration-tests/testapp/src/androidTest/java/android/arch/persistence/room/integration/testapp/vo/AvgWeightByAge.java
@@ -0,0 +1,87 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.arch.persistence.room.integration.testapp.vo;
+
+import android.arch.persistence.room.ColumnInfo;
+import android.arch.persistence.room.Ignore;
+
+@SuppressWarnings("unused")
+public class AvgWeightByAge {
+
+    private int mAge;
+
+    @ColumnInfo(name = "AVG(mWeight)")
+    private float mWeight;
+
+    public AvgWeightByAge() {
+    }
+
+    @Ignore
+    public AvgWeightByAge(int age, float weight) {
+        mAge = age;
+        mWeight = weight;
+    }
+
+    public int getAge() {
+        return mAge;
+    }
+
+    public void setAge(int age) {
+        mAge = age;
+    }
+
+    public float getWeight() {
+        return mWeight;
+    }
+
+    public void setWeight(float weight) {
+        mWeight = weight;
+    }
+
+    @Override
+    public String toString() {
+        return "AvgWeightByAge{"
+                + "mAge=" + mAge
+                + ", mWeight=" + mWeight
+                + '}';
+    }
+
+    @Override
+    public boolean equals(Object o) {
+        if (this == o) {
+            return true;
+        }
+        if (o == null || getClass() != o.getClass()) {
+            return false;
+        }
+
+        AvgWeightByAge that = (AvgWeightByAge) o;
+
+        //noinspection SimplifiableIfStatement
+        if (mAge != that.mAge) {
+            return false;
+        }
+        return Float.compare(that.mWeight, mWeight) == 0;
+    }
+
+    @Override
+    public int hashCode() {
+        int result = mAge;
+        result = 31 * result + (mWeight != +0.0f ? Float.floatToIntBits(mWeight) : 0);
+        return result;
+    }
+}
diff --git a/room/integration-tests/testapp/src/androidTest/java/android/arch/persistence/room/integration/testapp/vo/BlobEntity.java b/room/integration-tests/testapp/src/androidTest/java/android/arch/persistence/room/integration/testapp/vo/BlobEntity.java
new file mode 100644
index 0000000..134afc7
--- /dev/null
+++ b/room/integration-tests/testapp/src/androidTest/java/android/arch/persistence/room/integration/testapp/vo/BlobEntity.java
@@ -0,0 +1,32 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.arch.persistence.room.integration.testapp.vo;
+
+import android.arch.persistence.room.Entity;
+import android.arch.persistence.room.PrimaryKey;
+
+@Entity
+public class BlobEntity {
+    @PrimaryKey
+    public long id;
+    public byte[] content;
+
+    public BlobEntity(long id, byte[] content) {
+        this.id = id;
+        this.content = content;
+    }
+}
diff --git a/room/integration-tests/testapp/src/androidTest/java/android/arch/persistence/room/integration/testapp/vo/Coordinates.java b/room/integration-tests/testapp/src/androidTest/java/android/arch/persistence/room/integration/testapp/vo/Coordinates.java
new file mode 100644
index 0000000..e8cfd06
--- /dev/null
+++ b/room/integration-tests/testapp/src/androidTest/java/android/arch/persistence/room/integration/testapp/vo/Coordinates.java
@@ -0,0 +1,44 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.arch.persistence.room.integration.testapp.vo;
+
+public class Coordinates {
+    public double lat;
+    public double lng;
+
+    @Override
+    public boolean equals(Object o) {
+        if (this == o) return true;
+        if (o == null || getClass() != o.getClass()) return false;
+
+        Coordinates that = (Coordinates) o;
+
+        if (Double.compare(that.lat, lat) != 0) return false;
+        return Double.compare(that.lng, lng) == 0;
+    }
+
+    @Override
+    public int hashCode() {
+        int result;
+        long temp;
+        temp = Double.doubleToLongBits(lat);
+        result = (int) (temp ^ (temp >>> 32));
+        temp = Double.doubleToLongBits(lng);
+        result = 31 * result + (int) (temp ^ (temp >>> 32));
+        return result;
+    }
+}
diff --git a/room/integration-tests/testapp/src/androidTest/java/android/arch/persistence/room/integration/testapp/vo/IntAutoIncPKeyEntity.java b/room/integration-tests/testapp/src/androidTest/java/android/arch/persistence/room/integration/testapp/vo/IntAutoIncPKeyEntity.java
new file mode 100644
index 0000000..3392649
--- /dev/null
+++ b/room/integration-tests/testapp/src/androidTest/java/android/arch/persistence/room/integration/testapp/vo/IntAutoIncPKeyEntity.java
@@ -0,0 +1,27 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.arch.persistence.room.integration.testapp.vo;
+
+import android.arch.persistence.room.Entity;
+import android.arch.persistence.room.PrimaryKey;
+
+@Entity
+public class IntAutoIncPKeyEntity {
+    @PrimaryKey(autoGenerate = true)
+    public int pKey;
+    public String data;
+}
diff --git a/room/integration-tests/testapp/src/androidTest/java/android/arch/persistence/room/integration/testapp/vo/IntegerAutoIncPKeyEntity.java b/room/integration-tests/testapp/src/androidTest/java/android/arch/persistence/room/integration/testapp/vo/IntegerAutoIncPKeyEntity.java
new file mode 100644
index 0000000..636ffd9
--- /dev/null
+++ b/room/integration-tests/testapp/src/androidTest/java/android/arch/persistence/room/integration/testapp/vo/IntegerAutoIncPKeyEntity.java
@@ -0,0 +1,27 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.arch.persistence.room.integration.testapp.vo;
+
+import android.arch.persistence.room.Entity;
+import android.arch.persistence.room.PrimaryKey;
+
+@Entity
+public class IntegerAutoIncPKeyEntity {
+    @PrimaryKey(autoGenerate = true)
+    public Integer pKey;
+    public String data;
+}
diff --git a/room/integration-tests/testapp/src/androidTest/java/android/arch/persistence/room/integration/testapp/vo/Pet.java b/room/integration-tests/testapp/src/androidTest/java/android/arch/persistence/room/integration/testapp/vo/Pet.java
new file mode 100644
index 0000000..8806e10
--- /dev/null
+++ b/room/integration-tests/testapp/src/androidTest/java/android/arch/persistence/room/integration/testapp/vo/Pet.java
@@ -0,0 +1,74 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.arch.persistence.room.integration.testapp.vo;
+
+import android.arch.persistence.room.ColumnInfo;
+import android.arch.persistence.room.Entity;
+import android.arch.persistence.room.PrimaryKey;
+
+@Entity
+public class Pet {
+    @PrimaryKey
+    private int mPetId;
+    private int mUserId;
+    @ColumnInfo(name = "mPetName")
+    private String mName;
+
+    public int getPetId() {
+        return mPetId;
+    }
+
+    public void setPetId(int petId) {
+        mPetId = petId;
+    }
+
+    public String getName() {
+        return mName;
+    }
+
+    public void setName(String name) {
+        mName = name;
+    }
+
+    public int getUserId() {
+        return mUserId;
+    }
+
+    public void setUserId(int userId) {
+        mUserId = userId;
+    }
+
+    @Override
+    public boolean equals(Object o) {
+        if (this == o) return true;
+        if (o == null || getClass() != o.getClass()) return false;
+
+        Pet pet = (Pet) o;
+
+        if (mPetId != pet.mPetId) return false;
+        if (mUserId != pet.mUserId) return false;
+        return mName != null ? mName.equals(pet.mName) : pet.mName == null;
+    }
+
+    @Override
+    public int hashCode() {
+        int result = mPetId;
+        result = 31 * result + mUserId;
+        result = 31 * result + (mName != null ? mName.hashCode() : 0);
+        return result;
+    }
+}
diff --git a/room/integration-tests/testapp/src/androidTest/java/android/arch/persistence/room/integration/testapp/vo/PetAndToys.java b/room/integration-tests/testapp/src/androidTest/java/android/arch/persistence/room/integration/testapp/vo/PetAndToys.java
new file mode 100644
index 0000000..69fb591
--- /dev/null
+++ b/room/integration-tests/testapp/src/androidTest/java/android/arch/persistence/room/integration/testapp/vo/PetAndToys.java
@@ -0,0 +1,29 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.arch.persistence.room.integration.testapp.vo;
+
+import android.arch.persistence.room.Embedded;
+import android.arch.persistence.room.Relation;
+
+import java.util.List;
+
+public class PetAndToys {
+    @Embedded
+    public Pet pet;
+    @Relation(parentColumn = "mPetId", entityColumn = "mPetId")
+    public List<Toy> toys;
+}
diff --git a/room/integration-tests/testapp/src/androidTest/java/android/arch/persistence/room/integration/testapp/vo/PetCouple.java b/room/integration-tests/testapp/src/androidTest/java/android/arch/persistence/room/integration/testapp/vo/PetCouple.java
new file mode 100644
index 0000000..f27b131
--- /dev/null
+++ b/room/integration-tests/testapp/src/androidTest/java/android/arch/persistence/room/integration/testapp/vo/PetCouple.java
@@ -0,0 +1,59 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.arch.persistence.room.integration.testapp.vo;
+
+import android.arch.persistence.room.Embedded;
+import android.arch.persistence.room.Entity;
+import android.arch.persistence.room.PrimaryKey;
+import android.arch.persistence.room.RoomWarnings;
+
+@Entity
+@SuppressWarnings(RoomWarnings.PRIMARY_KEY_FROM_EMBEDDED_IS_DROPPED)
+public class PetCouple {
+    @PrimaryKey
+    public String id;
+    @Embedded(prefix = "male_")
+    public Pet male;
+    @Embedded(prefix = "female_")
+    private Pet mFemale;
+
+    public Pet getFemale() {
+        return mFemale;
+    }
+
+    public void setFemale(Pet female) {
+        mFemale = female;
+    }
+
+    @Override
+    public boolean equals(Object o) {
+        if (this == o) return true;
+        if (o == null || getClass() != o.getClass()) return false;
+
+        PetCouple petCouple = (PetCouple) o;
+
+        if (male != null ? !male.equals(petCouple.male) : petCouple.male != null) return false;
+        return mFemale != null ? mFemale.equals(petCouple.mFemale) : petCouple.mFemale == null;
+    }
+
+    @Override
+    public int hashCode() {
+        int result = male != null ? male.hashCode() : 0;
+        result = 31 * result + (mFemale != null ? mFemale.hashCode() : 0);
+        return result;
+    }
+}
diff --git a/room/integration-tests/testapp/src/androidTest/java/android/arch/persistence/room/integration/testapp/vo/School.java b/room/integration-tests/testapp/src/androidTest/java/android/arch/persistence/room/integration/testapp/vo/School.java
new file mode 100644
index 0000000..f0426f1
--- /dev/null
+++ b/room/integration-tests/testapp/src/androidTest/java/android/arch/persistence/room/integration/testapp/vo/School.java
@@ -0,0 +1,95 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.arch.persistence.room.integration.testapp.vo;
+
+import android.arch.persistence.room.Embedded;
+import android.arch.persistence.room.Entity;
+import android.arch.persistence.room.PrimaryKey;
+
+@Entity
+public class School {
+    @PrimaryKey
+    private int mId;
+    private String mName;
+    @Embedded(prefix = "address_")
+    public Address address;
+
+    @Embedded(prefix = "manager_")
+    private User mManager;
+
+    public int getId() {
+        return mId;
+    }
+
+    public void setId(int id) {
+        mId = id;
+    }
+
+    public Address getAddress() {
+        return address;
+    }
+
+    public void setAddress(Address address) {
+        this.address = address;
+    }
+
+    public User getManager() {
+        return mManager;
+    }
+
+    public void setManager(User manager) {
+        mManager = manager;
+    }
+
+    public String getName() {
+        return mName;
+    }
+
+    public void setName(String name) {
+        mName = name;
+    }
+
+    @Override
+    public boolean equals(Object o) {
+        if (this == o) return true;
+        if (o == null || getClass() != o.getClass()) {
+            return false;
+        }
+
+        School school = (School) o;
+
+        if (mId != school.mId) {
+            return false;
+        }
+        if (mName != null ? !mName.equals(school.mName) : school.mName != null) {
+            return false;
+        }
+        if (address != null ? !address.equals(school.address) : school.address != null) {
+            return false;
+        }
+        return mManager != null ? mManager.equals(school.mManager) : school.mManager == null;
+    }
+
+    @Override
+    public int hashCode() {
+        int result = mId;
+        result = 31 * result + (mName != null ? mName.hashCode() : 0);
+        result = 31 * result + (address != null ? address.hashCode() : 0);
+        result = 31 * result + (mManager != null ? mManager.hashCode() : 0);
+        return result;
+    }
+}
diff --git a/room/integration-tests/testapp/src/androidTest/java/android/arch/persistence/room/integration/testapp/vo/SchoolRef.java b/room/integration-tests/testapp/src/androidTest/java/android/arch/persistence/room/integration/testapp/vo/SchoolRef.java
new file mode 100644
index 0000000..bbf0cb4
--- /dev/null
+++ b/room/integration-tests/testapp/src/androidTest/java/android/arch/persistence/room/integration/testapp/vo/SchoolRef.java
@@ -0,0 +1,20 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.arch.persistence.room.integration.testapp.vo;
+
+public class SchoolRef extends School {
+}
diff --git a/room/integration-tests/testapp/src/androidTest/java/android/arch/persistence/room/integration/testapp/vo/Toy.java b/room/integration-tests/testapp/src/androidTest/java/android/arch/persistence/room/integration/testapp/vo/Toy.java
new file mode 100644
index 0000000..5198c2d
--- /dev/null
+++ b/room/integration-tests/testapp/src/androidTest/java/android/arch/persistence/room/integration/testapp/vo/Toy.java
@@ -0,0 +1,75 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.arch.persistence.room.integration.testapp.vo;
+
+import android.arch.persistence.room.Entity;
+import android.arch.persistence.room.PrimaryKey;
+
+/**
+ * The toys of a pet.
+ */
+@Entity
+public class Toy {
+    @PrimaryKey(autoGenerate = true)
+    private int mId;
+    private String mName;
+    private int mPetId;
+
+    public int getId() {
+        return mId;
+    }
+
+    public void setId(int id) {
+        mId = id;
+    }
+
+    public String getName() {
+        return mName;
+    }
+
+    public void setName(String name) {
+        mName = name;
+    }
+
+    public int getPetId() {
+        return mPetId;
+    }
+
+    public void setPetId(int petId) {
+        mPetId = petId;
+    }
+
+    @Override
+    public boolean equals(Object o) {
+        if (this == o) return true;
+        if (o == null || getClass() != o.getClass()) return false;
+
+        Toy toy = (Toy) o;
+
+        if (mId != toy.mId) return false;
+        if (mPetId != toy.mPetId) return false;
+        return mName != null ? mName.equals(toy.mName) : toy.mName == null;
+    }
+
+    @Override
+    public int hashCode() {
+        int result = mId;
+        result = 31 * result + (mName != null ? mName.hashCode() : 0);
+        result = 31 * result + mPetId;
+        return result;
+    }
+}
diff --git a/room/integration-tests/testapp/src/androidTest/java/android/arch/persistence/room/integration/testapp/vo/User.java b/room/integration-tests/testapp/src/androidTest/java/android/arch/persistence/room/integration/testapp/vo/User.java
new file mode 100644
index 0000000..57cf585
--- /dev/null
+++ b/room/integration-tests/testapp/src/androidTest/java/android/arch/persistence/room/integration/testapp/vo/User.java
@@ -0,0 +1,161 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.arch.persistence.room.integration.testapp.vo;
+
+import android.arch.persistence.room.ColumnInfo;
+import android.arch.persistence.room.Entity;
+import android.arch.persistence.room.PrimaryKey;
+import android.arch.persistence.room.TypeConverters;
+import android.arch.persistence.room.integration.testapp.TestDatabase;
+
+import java.util.Date;
+
+@Entity
+@TypeConverters({TestDatabase.Converters.class})
+public class User {
+
+    @PrimaryKey
+    private int mId;
+
+    private String mName;
+
+    private String mLastName;
+
+    private int mAge;
+
+    private boolean mAdmin;
+
+    private float mWeight;
+
+    private Date mBirthday;
+
+    @ColumnInfo(name = "custommm")
+    private String mCustomField;
+
+    public int getId() {
+        return mId;
+    }
+
+    public void setId(int id) {
+        this.mId = id;
+    }
+
+    public String getName() {
+        return mName;
+    }
+
+    public void setName(String name) {
+        this.mName = name;
+    }
+
+    public String getLastName() {
+        return mLastName;
+    }
+
+    public void setLastName(String lastName) {
+        this.mLastName = lastName;
+    }
+
+    public int getAge() {
+        return mAge;
+    }
+
+    public void setAge(int age) {
+        this.mAge = age;
+    }
+
+    public boolean isAdmin() {
+        return mAdmin;
+    }
+
+    public void setAdmin(boolean admin) {
+        mAdmin = admin;
+    }
+
+    public float getWeight() {
+        return mWeight;
+    }
+
+    public void setWeight(float weight) {
+        mWeight = weight;
+    }
+
+    public Date getBirthday() {
+        return mBirthday;
+    }
+
+    public void setBirthday(Date birthday) {
+        mBirthday = birthday;
+    }
+
+    public String getCustomField() {
+        return mCustomField;
+    }
+
+    public void setCustomField(String customField) {
+        mCustomField = customField;
+    }
+
+    @Override
+    public boolean equals(Object o) {
+        if (this == o) return true;
+        if (o == null || getClass() != o.getClass()) return false;
+
+        User user = (User) o;
+
+        if (mId != user.mId) return false;
+        if (mAge != user.mAge) return false;
+        if (mAdmin != user.mAdmin) return false;
+        if (Float.compare(user.mWeight, mWeight) != 0) return false;
+        if (mName != null ? !mName.equals(user.mName) : user.mName != null) return false;
+        if (mLastName != null ? !mLastName.equals(user.mLastName) : user.mLastName != null) {
+            return false;
+        }
+        if (mBirthday != null ? !mBirthday.equals(user.mBirthday) : user.mBirthday != null) {
+            return false;
+        }
+        return mCustomField != null ? mCustomField.equals(user.mCustomField)
+                : user.mCustomField == null;
+    }
+
+    @Override
+    public int hashCode() {
+        int result = mId;
+        result = 31 * result + (mName != null ? mName.hashCode() : 0);
+        result = 31 * result + (mLastName != null ? mLastName.hashCode() : 0);
+        result = 31 * result + mAge;
+        result = 31 * result + (mAdmin ? 1 : 0);
+        result = 31 * result + (mWeight != +0.0f ? Float.floatToIntBits(mWeight) : 0);
+        result = 31 * result + (mBirthday != null ? mBirthday.hashCode() : 0);
+        result = 31 * result + (mCustomField != null ? mCustomField.hashCode() : 0);
+        return result;
+    }
+
+    @Override
+    public String toString() {
+        return "User{"
+                + "mId=" + mId
+                + ", mName='" + mName + '\''
+                + ", mLastName='" + mLastName + '\''
+                + ", mAge=" + mAge
+                + ", mAdmin=" + mAdmin
+                + ", mWeight=" + mWeight
+                + ", mBirthday=" + mBirthday
+                + ", mCustom=" + mCustomField
+                + '}';
+    }
+}
diff --git a/room/integration-tests/testapp/src/androidTest/java/android/arch/persistence/room/integration/testapp/vo/UserAndAllPets.java b/room/integration-tests/testapp/src/androidTest/java/android/arch/persistence/room/integration/testapp/vo/UserAndAllPets.java
new file mode 100644
index 0000000..24a0710
--- /dev/null
+++ b/room/integration-tests/testapp/src/androidTest/java/android/arch/persistence/room/integration/testapp/vo/UserAndAllPets.java
@@ -0,0 +1,29 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.arch.persistence.room.integration.testapp.vo;
+
+import android.arch.persistence.room.Embedded;
+import android.arch.persistence.room.Relation;
+
+import java.util.List;
+
+public class UserAndAllPets {
+    @Embedded
+    public User user;
+    @Relation(parentColumn = "mId", entityColumn = "mUserId")
+    public List<Pet> pets;
+}
diff --git a/room/integration-tests/testapp/src/androidTest/java/android/arch/persistence/room/integration/testapp/vo/UserAndPet.java b/room/integration-tests/testapp/src/androidTest/java/android/arch/persistence/room/integration/testapp/vo/UserAndPet.java
new file mode 100644
index 0000000..628e9bf
--- /dev/null
+++ b/room/integration-tests/testapp/src/androidTest/java/android/arch/persistence/room/integration/testapp/vo/UserAndPet.java
@@ -0,0 +1,41 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.arch.persistence.room.integration.testapp.vo;
+import android.arch.persistence.room.Embedded;
+
+public class UserAndPet {
+    @Embedded
+    private User mUser;
+    @Embedded
+    private Pet mPet;
+
+    public User getUser() {
+        return mUser;
+    }
+
+    public void setUser(User user) {
+        mUser = user;
+    }
+
+    public Pet getPet() {
+        return mPet;
+    }
+
+    public void setPet(Pet pet) {
+        mPet = pet;
+    }
+}
diff --git a/room/integration-tests/testapp/src/androidTest/java/android/arch/persistence/room/integration/testapp/vo/UserAndPetNonNull.java b/room/integration-tests/testapp/src/androidTest/java/android/arch/persistence/room/integration/testapp/vo/UserAndPetNonNull.java
new file mode 100644
index 0000000..8739bd0
--- /dev/null
+++ b/room/integration-tests/testapp/src/androidTest/java/android/arch/persistence/room/integration/testapp/vo/UserAndPetNonNull.java
@@ -0,0 +1,44 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.arch.persistence.room.integration.testapp.vo;
+
+import android.arch.persistence.room.Embedded;
+import android.support.annotation.NonNull;
+
+public class UserAndPetNonNull {
+    @Embedded
+    private User mUser;
+    @Embedded
+    @NonNull
+    private Pet mPet;
+
+    public User getUser() {
+        return mUser;
+    }
+
+    public void setUser(User user) {
+        mUser = user;
+    }
+
+    public Pet getPet() {
+        return mPet;
+    }
+
+    public void setPet(Pet pet) {
+        mPet = pet;
+    }
+}
diff --git a/room/integration-tests/testapp/src/androidTest/java/android/arch/persistence/room/integration/testapp/vo/UserIdAndPetNames.java b/room/integration-tests/testapp/src/androidTest/java/android/arch/persistence/room/integration/testapp/vo/UserIdAndPetNames.java
new file mode 100644
index 0000000..444431e
--- /dev/null
+++ b/room/integration-tests/testapp/src/androidTest/java/android/arch/persistence/room/integration/testapp/vo/UserIdAndPetNames.java
@@ -0,0 +1,33 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.arch.persistence.room.integration.testapp.vo;
+
+import android.arch.persistence.room.ColumnInfo;
+import android.arch.persistence.room.Relation;
+
+import java.util.List;
+
+/**
+ * Same as Pet class but only keeps name and user id
+ */
+public class UserIdAndPetNames {
+    @ColumnInfo(name = "mId")
+    public int userId;
+    @Relation(entity = Pet.class, parentColumn = "mId", entityColumn = "mUserId",
+            projection = "mPetName")
+    public List<String> names;
+}
diff --git a/room/integration-tests/testapp/src/androidTest/java/android/arch/persistence/room/integration/testapp/vo/UserWithPetsAndToys.java b/room/integration-tests/testapp/src/androidTest/java/android/arch/persistence/room/integration/testapp/vo/UserWithPetsAndToys.java
new file mode 100644
index 0000000..01c9bed
--- /dev/null
+++ b/room/integration-tests/testapp/src/androidTest/java/android/arch/persistence/room/integration/testapp/vo/UserWithPetsAndToys.java
@@ -0,0 +1,29 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.arch.persistence.room.integration.testapp.vo;
+
+import android.arch.persistence.room.Embedded;
+import android.arch.persistence.room.Relation;
+
+import java.util.List;
+
+public class UserWithPetsAndToys {
+    @Embedded
+    public User user;
+    @Relation(entity = Pet.class, parentColumn = "mId", entityColumn = "mUserId")
+    public List<PetAndToys> pets;
+}
diff --git a/room/integration-tests/testapp/src/main/AndroidManifest.xml b/room/integration-tests/testapp/src/main/AndroidManifest.xml
new file mode 100644
index 0000000..67457f9
--- /dev/null
+++ b/room/integration-tests/testapp/src/main/AndroidManifest.xml
@@ -0,0 +1,22 @@
+<!--
+  ~ Copyright (C) 2016 The Android Open Source Project
+  ~
+  ~ Licensed under the Apache License, Version 2.0 (the "License");
+  ~ you may not use this file except in compliance with the License.
+  ~ You may obtain a copy of the License at
+  ~
+  ~      http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing, software
+  ~ distributed under the License is distributed on an "AS IS" BASIS,
+  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  ~ See the License for the specific language governing permissions and
+  ~ limitations under the License.
+  -->
+
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+          package="android.arch.persistence.room.integration.testapp">
+    <application android:allowBackup="true"
+                 android:debuggable="true"
+                 android:supportsRtl="true"/>
+</manifest>
diff --git a/room/migration/build.gradle b/room/migration/build.gradle
new file mode 100644
index 0000000..7e3794b
--- /dev/null
+++ b/room/migration/build.gradle
@@ -0,0 +1,35 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+apply plugin: 'maven'
+apply plugin: 'java'
+
+sourceCompatibility = 1.7
+
+sourceSets {
+    test.java.srcDirs += 'src/tests/kotlin'
+}
+project.ext.noDocs = true
+dependencies {
+    compile project(":room:common")
+    compile libs.kotlin.stdlib
+    compile libs.gson
+    testCompile libs.junit
+    testCompile libs.ij_annotations
+    testCompile libs.mockito_core
+}
+
+archivesBaseName = "migration"
diff --git a/room/migration/src/main/java/android/arch/persistence/room/migration/bundle/BundleUtil.java b/room/migration/src/main/java/android/arch/persistence/room/migration/bundle/BundleUtil.java
new file mode 100644
index 0000000..4356e69
--- /dev/null
+++ b/room/migration/src/main/java/android/arch/persistence/room/migration/bundle/BundleUtil.java
@@ -0,0 +1,36 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.arch.persistence.room.migration.bundle;
+
+import android.support.annotation.RestrictTo;
+
+/**
+ * Utility functions for bundling.
+ *
+ * @hide
+ */
+@RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
+public class BundleUtil {
+    /**
+     * Placeholder for table names in queries.
+     */
+    public static final String TABLE_NAME_PLACEHOLDER = "${TABLE_NAME}";
+
+    static String replaceTableName(String contents, String tableName) {
+        return contents.replace(TABLE_NAME_PLACEHOLDER, tableName);
+    }
+}
diff --git a/room/migration/src/main/java/android/arch/persistence/room/migration/bundle/DatabaseBundle.java b/room/migration/src/main/java/android/arch/persistence/room/migration/bundle/DatabaseBundle.java
new file mode 100644
index 0000000..4ac9029
--- /dev/null
+++ b/room/migration/src/main/java/android/arch/persistence/room/migration/bundle/DatabaseBundle.java
@@ -0,0 +1,107 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.arch.persistence.room.migration.bundle;
+
+import android.support.annotation.RestrictTo;
+
+import com.google.gson.annotations.SerializedName;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * Data class that holds the schema information for a
+ * {@link android.arch.persistence.room.Database Database}.
+ *
+ * @hide
+ */
+@RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
+public class DatabaseBundle {
+    @SerializedName("version")
+    private int mVersion;
+    @SerializedName("identityHash")
+    private String mIdentityHash;
+    @SerializedName("entities")
+    private List<EntityBundle> mEntities;
+    // then entity where we keep room information
+    @SerializedName("setupQueries")
+    private List<String> mSetupQueries;
+    private transient Map<String, EntityBundle> mEntitiesByTableName;
+
+    /**
+     * Creates a new database
+     * @param version Version
+     * @param identityHash Identity hash
+     * @param entities List of entities
+     */
+    public DatabaseBundle(int version, String identityHash, List<EntityBundle> entities,
+            List<String> setupQueries) {
+        mVersion = version;
+        mIdentityHash = identityHash;
+        mEntities = entities;
+        mSetupQueries = setupQueries;
+    }
+
+    /**
+     * @return The identity has of the Database.
+     */
+    public String getIdentityHash() {
+        return mIdentityHash;
+    }
+
+    /**
+     * @return The database version.
+     */
+    public int getVersion() {
+        return mVersion;
+    }
+
+    /**
+     * @return List of entities.
+     */
+    public List<EntityBundle> getEntities() {
+        return mEntities;
+    }
+
+    /**
+     * @return Map of entities, keyed by table name.
+     */
+    @SuppressWarnings("unused")
+    public Map<String, EntityBundle> getEntitiesByTableName() {
+        if (mEntitiesByTableName == null) {
+            mEntitiesByTableName = new HashMap<>();
+            for (EntityBundle bundle : mEntities) {
+                mEntitiesByTableName.put(bundle.getTableName(), bundle);
+            }
+        }
+        return mEntitiesByTableName;
+    }
+
+    /**
+     * @return List of SQL queries to build this database from scratch.
+     */
+    public List<String> buildCreateQueries() {
+        List<String> result = new ArrayList<>();
+        for (EntityBundle entityBundle : mEntities) {
+            result.addAll(entityBundle.buildCreateQueries());
+        }
+        result.addAll(mSetupQueries);
+        return result;
+    }
+}
diff --git a/room/migration/src/main/java/android/arch/persistence/room/migration/bundle/EntityBundle.java b/room/migration/src/main/java/android/arch/persistence/room/migration/bundle/EntityBundle.java
new file mode 100644
index 0000000..fc39a6a
--- /dev/null
+++ b/room/migration/src/main/java/android/arch/persistence/room/migration/bundle/EntityBundle.java
@@ -0,0 +1,179 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.arch.persistence.room.migration.bundle;
+
+import android.support.annotation.RestrictTo;
+
+import com.google.gson.annotations.SerializedName;
+
+import org.jetbrains.annotations.NotNull;
+
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * Data class that holds the schema information about an
+ * {@link android.arch.persistence.room.Entity Entity}.
+ *
+ * @hide
+ */
+@RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
+public class EntityBundle {
+
+    static final String NEW_TABLE_PREFIX = "_new_";
+
+    @SerializedName("tableName")
+    private String mTableName;
+    @SerializedName("createSql")
+    private String mCreateSql;
+    @SerializedName("fields")
+    private List<FieldBundle> mFields;
+    @SerializedName("primaryKey")
+    private PrimaryKeyBundle mPrimaryKey;
+    @SerializedName("indices")
+    private List<IndexBundle> mIndices;
+    @SerializedName("foreignKeys")
+    private List<ForeignKeyBundle> mForeignKeys;
+
+    private transient String mNewTableName;
+    private transient Map<String, FieldBundle> mFieldsByColumnName;
+
+    /**
+     * Creates a new bundle.
+     *
+     * @param tableName The table name.
+     * @param createSql Create query with the table name placeholder.
+     * @param fields The list of fields.
+     * @param primaryKey The primary key.
+     * @param indices The list of indices
+     * @param foreignKeys The list of foreign keys
+     */
+    public EntityBundle(String tableName, String createSql,
+            List<FieldBundle> fields,
+            PrimaryKeyBundle primaryKey,
+            List<IndexBundle> indices,
+            List<ForeignKeyBundle> foreignKeys) {
+        mTableName = tableName;
+        mCreateSql = createSql;
+        mFields = fields;
+        mPrimaryKey = primaryKey;
+        mIndices = indices;
+        mForeignKeys = foreignKeys;
+    }
+
+    /**
+     * @return The table name if it is created during a table schema modification.
+     */
+    public String getNewTableName() {
+        if (mNewTableName == null) {
+            mNewTableName = NEW_TABLE_PREFIX + mTableName;
+        }
+        return mNewTableName;
+    }
+
+    /**
+     * @return Map of fields keyed by their column names.
+     */
+    public Map<String, FieldBundle> getFieldsByColumnName() {
+        if (mFieldsByColumnName == null) {
+            mFieldsByColumnName = new HashMap<>();
+            for (FieldBundle bundle : mFields) {
+                mFieldsByColumnName.put(bundle.getColumnName(), bundle);
+            }
+        }
+        return mFieldsByColumnName;
+    }
+
+    /**
+     * @return The table name.
+     */
+    public String getTableName() {
+        return mTableName;
+    }
+
+    /**
+     * @return The create query with table name placeholder.
+     */
+    public String getCreateSql() {
+        return mCreateSql;
+    }
+
+    /**
+     * @return List of fields.
+     */
+    public List<FieldBundle> getFields() {
+        return mFields;
+    }
+
+    /**
+     * @return The primary key description.
+     */
+    public PrimaryKeyBundle getPrimaryKey() {
+        return mPrimaryKey;
+    }
+
+    /**
+     * @return List of indices.
+     */
+    public List<IndexBundle> getIndices() {
+        return mIndices;
+    }
+
+    /**
+     * @return List of foreign keys.
+     */
+    public List<ForeignKeyBundle> getForeignKeys() {
+        return mForeignKeys;
+    }
+
+    /**
+     * @return Create table SQL query that uses the actual table name.
+     */
+    public String createTable() {
+        return BundleUtil.replaceTableName(mCreateSql, getTableName());
+    }
+
+    /**
+     * @return Create table SQL query that uses the table name with "new" prefix.
+     */
+    public String createNewTable() {
+        return BundleUtil.replaceTableName(mCreateSql, getNewTableName());
+    }
+
+    /**
+     * @return Renames the table with {@link #getNewTableName()} to {@link #getTableName()}.
+     */
+    @NotNull
+    public String renameToOriginal() {
+        return "ALTER TABLE " + getNewTableName() + " RENAME TO " + getTableName();
+    }
+
+    /**
+     * @return Creates the list of SQL queries that are necessary to create this entitiy.
+     */
+    public Collection<String> buildCreateQueries() {
+        List<String> result = new ArrayList<>();
+        result.add(createTable());
+        for (IndexBundle indexBundle : mIndices) {
+            result.add(indexBundle.create(getTableName()));
+        }
+        return result;
+    }
+}
diff --git a/room/migration/src/main/java/android/arch/persistence/room/migration/bundle/FieldBundle.java b/room/migration/src/main/java/android/arch/persistence/room/migration/bundle/FieldBundle.java
new file mode 100644
index 0000000..3e8fd97
--- /dev/null
+++ b/room/migration/src/main/java/android/arch/persistence/room/migration/bundle/FieldBundle.java
@@ -0,0 +1,55 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.arch.persistence.room.migration.bundle;
+
+import android.support.annotation.RestrictTo;
+
+import com.google.gson.annotations.SerializedName;
+
+/**
+ * Data class that holds the schema information for an
+ * {@link android.arch.persistence.room.Entity Entity} field.
+ *
+ * @hide
+ */
+@RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
+public class FieldBundle {
+    @SerializedName("fieldPath")
+    private String mFieldPath;
+    @SerializedName("columnName")
+    private String mColumnName;
+    @SerializedName("affinity")
+    private String mAffinity;
+
+    public FieldBundle(String fieldPath, String columnName, String affinity) {
+        mFieldPath = fieldPath;
+        mColumnName = columnName;
+        mAffinity = affinity;
+    }
+
+    public String getFieldPath() {
+        return mFieldPath;
+    }
+
+    public String getColumnName() {
+        return mColumnName;
+    }
+
+    public String getAffinity() {
+        return mAffinity;
+    }
+}
diff --git a/room/migration/src/main/java/android/arch/persistence/room/migration/bundle/ForeignKeyBundle.java b/room/migration/src/main/java/android/arch/persistence/room/migration/bundle/ForeignKeyBundle.java
new file mode 100644
index 0000000..1467a4f
--- /dev/null
+++ b/room/migration/src/main/java/android/arch/persistence/room/migration/bundle/ForeignKeyBundle.java
@@ -0,0 +1,100 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.arch.persistence.room.migration.bundle;
+
+import com.google.gson.annotations.SerializedName;
+
+import java.util.List;
+
+/**
+ * Holds the information about a foreign key reference.
+ */
+public class ForeignKeyBundle {
+    @SerializedName("table")
+    private String mTable;
+    @SerializedName("onDelete")
+    private String mOnDelete;
+    @SerializedName("onUpdate")
+    private String mOnUpdate;
+    @SerializedName("columns")
+    private List<String> mColumns;
+    @SerializedName("referencedColumns")
+    private List<String> mReferencedColumns;
+
+    /**
+     * Creates a foreign key bundle with the given parameters.
+     *
+     * @param table The target table
+     * @param onDelete OnDelete action
+     * @param onUpdate OnUpdate action
+     * @param columns The list of columns in the current table
+     * @param referencedColumns The list of columns in the referenced table
+     */
+    public ForeignKeyBundle(String table, String onDelete, String onUpdate,
+            List<String> columns, List<String> referencedColumns) {
+        mTable = table;
+        mOnDelete = onDelete;
+        mOnUpdate = onUpdate;
+        mColumns = columns;
+        mReferencedColumns = referencedColumns;
+    }
+
+    /**
+     * Returns the table name
+     *
+     * @return Returns the table name
+     */
+    public String getTable() {
+        return mTable;
+    }
+
+    /**
+     * Returns the SQLite foreign key action that will be performed when referenced row is deleted.
+     *
+     * @return The SQLite on delete action
+     */
+    public String getOnDelete() {
+        return mOnDelete;
+    }
+
+    /**
+     * Returns the SQLite foreign key action that will be performed when referenced row is updated.
+     *
+     * @return The SQLite on update action
+     */
+    public String getOnUpdate() {
+        return mOnUpdate;
+    }
+
+    /**
+     * Returns the ordered list of columns in the current table.
+     *
+     * @return The list of columns in the current entity.
+     */
+    public List<String> getColumns() {
+        return mColumns;
+    }
+
+    /**
+     * Returns the ordered list of columns in the referenced table.
+     *
+     * @return The list of columns in the referenced entity.
+     */
+    public List<String> getReferencedColumns() {
+        return mReferencedColumns;
+    }
+}
diff --git a/room/migration/src/main/java/android/arch/persistence/room/migration/bundle/IndexBundle.java b/room/migration/src/main/java/android/arch/persistence/room/migration/bundle/IndexBundle.java
new file mode 100644
index 0000000..ba40618
--- /dev/null
+++ b/room/migration/src/main/java/android/arch/persistence/room/migration/bundle/IndexBundle.java
@@ -0,0 +1,68 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.arch.persistence.room.migration.bundle;
+
+import android.support.annotation.RestrictTo;
+
+import com.google.gson.annotations.SerializedName;
+
+import java.util.List;
+
+/**
+ * Data class that holds the schema information about a table Index.
+ *
+ * @hide
+ */
+@RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
+public class IndexBundle {
+    @SerializedName("name")
+    private String mName;
+    @SerializedName("unique")
+    private boolean mUnique;
+    @SerializedName("columnNames")
+    private List<String> mColumnNames;
+    @SerializedName("createSql")
+    private String mCreateSql;
+
+    public IndexBundle(String name, boolean unique, List<String> columnNames,
+            String createSql) {
+        mName = name;
+        mUnique = unique;
+        mColumnNames = columnNames;
+        mCreateSql = createSql;
+    }
+
+    public String getName() {
+        return mName;
+    }
+
+    public boolean isUnique() {
+        return mUnique;
+    }
+
+    public List<String> getColumnNames() {
+        return mColumnNames;
+    }
+
+    /**
+     * @hide
+     */
+    @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
+    public String create(String tableName) {
+        return BundleUtil.replaceTableName(mCreateSql, tableName);
+    }
+}
diff --git a/room/migration/src/main/java/android/arch/persistence/room/migration/bundle/PrimaryKeyBundle.java b/room/migration/src/main/java/android/arch/persistence/room/migration/bundle/PrimaryKeyBundle.java
new file mode 100644
index 0000000..c16f967
--- /dev/null
+++ b/room/migration/src/main/java/android/arch/persistence/room/migration/bundle/PrimaryKeyBundle.java
@@ -0,0 +1,49 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.arch.persistence.room.migration.bundle;
+
+import android.support.annotation.RestrictTo;
+
+import com.google.gson.annotations.SerializedName;
+
+import java.util.List;
+
+/**
+ * Data class that holds the schema information about a primary key.
+ *
+ * @hide
+ */
+@RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
+public class PrimaryKeyBundle {
+    @SerializedName("columnNames")
+    private List<String> mColumnNames;
+    @SerializedName("autoGenerate")
+    private boolean mAutoGenerate;
+
+    public PrimaryKeyBundle(boolean autoGenerate, List<String> columnNames) {
+        mColumnNames = columnNames;
+        mAutoGenerate = autoGenerate;
+    }
+
+    public List<String> getColumnNames() {
+        return mColumnNames;
+    }
+
+    public boolean isAutoGenerate() {
+        return mAutoGenerate;
+    }
+}
diff --git a/room/migration/src/main/java/android/arch/persistence/room/migration/bundle/SchemaBundle.java b/room/migration/src/main/java/android/arch/persistence/room/migration/bundle/SchemaBundle.java
new file mode 100644
index 0000000..d6171aa
--- /dev/null
+++ b/room/migration/src/main/java/android/arch/persistence/room/migration/bundle/SchemaBundle.java
@@ -0,0 +1,107 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.arch.persistence.room.migration.bundle;
+
+import android.support.annotation.RestrictTo;
+
+import com.google.gson.Gson;
+import com.google.gson.GsonBuilder;
+import com.google.gson.annotations.SerializedName;
+
+import java.io.Closeable;
+import java.io.File;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.InputStreamReader;
+import java.io.OutputStreamWriter;
+import java.io.UnsupportedEncodingException;
+
+/**
+ * Data class that holds the information about a database schema export.
+ *
+ * @hide
+ */
+@RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
+public class SchemaBundle {
+
+    @SerializedName("formatVersion")
+    private int mFormatVersion;
+    @SerializedName("database")
+    private DatabaseBundle mDatabase;
+
+    private static final Gson GSON;
+    private static final String CHARSET = "UTF-8";
+    public static final int LATEST_FORMAT = 1;
+    static {
+        GSON = new GsonBuilder().setPrettyPrinting().disableHtmlEscaping().create();
+    }
+
+    public SchemaBundle(int formatVersion, DatabaseBundle database) {
+        mFormatVersion = formatVersion;
+        mDatabase = database;
+    }
+
+    @SuppressWarnings("unused")
+    public int getFormatVersion() {
+        return mFormatVersion;
+    }
+
+    public DatabaseBundle getDatabase() {
+        return mDatabase;
+    }
+
+    /**
+     * @hide
+     */
+    @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
+    public static SchemaBundle deserialize(InputStream fis)
+            throws UnsupportedEncodingException {
+        InputStreamReader is = new InputStreamReader(fis, CHARSET);
+        try {
+            return GSON.fromJson(is, SchemaBundle.class);
+        } finally {
+            safeClose(is);
+            safeClose(fis);
+        }
+    }
+
+    /**
+     * @hide
+     */
+    @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
+    public static void serialize(SchemaBundle bundle, File file) throws IOException {
+        FileOutputStream fos = new FileOutputStream(file, false);
+        OutputStreamWriter osw = new OutputStreamWriter(fos, CHARSET);
+        try {
+            GSON.toJson(bundle, osw);
+        } finally {
+            safeClose(osw);
+            safeClose(fos);
+        }
+    }
+
+    private static void safeClose(Closeable closeable) {
+        if (closeable != null) {
+            try {
+                closeable.close();
+            } catch (Throwable ignored) {
+            }
+        }
+    }
+
+}
diff --git a/room/runtime/build.gradle b/room/runtime/build.gradle
new file mode 100644
index 0000000..2bf1baf
--- /dev/null
+++ b/room/runtime/build.gradle
@@ -0,0 +1,87 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+import com.android.builder.core.BuilderConstants
+apply plugin: 'com.android.library'
+apply plugin: 'maven'
+
+android {
+    compileSdkVersion tools.current_sdk
+    buildToolsVersion tools.build_tools_version
+
+    defaultConfig {
+        minSdkVersion flatfoot.min_sdk
+        targetSdkVersion tools.current_sdk
+        versionCode 1
+        versionName "1.0"
+
+        testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
+
+    }
+
+    buildTypes.all {
+        consumerProguardFiles 'proguard-rules.pro'
+    }
+
+    buildTypes {
+        release {
+            minifyEnabled false
+        }
+    }
+
+    testOptions {
+        unitTests.returnDefaultValues = true
+    }
+    compileOptions {
+        sourceCompatibility JavaVersion.VERSION_1_7
+        targetCompatibility JavaVersion.VERSION_1_7
+    }
+}
+
+dependencies {
+    compile project(":room:common")
+    compile project(":room:db")
+    compile project(":room:db-impl")
+    compile project(":arch:runtime")
+    compile libs.support.core_utils
+
+
+    testCompile project(":arch:core-testing")
+    testCompile libs.junit
+    testCompile libs.mockito_core
+    testCompile libs.support.annotations
+
+    androidTestCompile libs.junit
+    androidTestCompile(libs.test_runner) {
+        exclude module: 'support-annotations'
+    }
+    androidTestCompile(libs.espresso_core, {
+        exclude group: 'com.android.support', module: 'support-annotations'
+    })
+}
+
+archivesBaseName = "runtime"
+
+createAndroidCheckstyle(project)
+
+android.libraryVariants.all { variant ->
+    def name = variant.buildType.name
+    def suffix = name.capitalize()
+    project.tasks.create(name: "jar${suffix}", type: Jar){
+        dependsOn variant.javaCompile
+        from variant.javaCompile.destinationDir
+        destinationDir new File(project.buildDir, "libJar")
+    }
+}
diff --git a/room/runtime/proguard-rules.pro b/room/runtime/proguard-rules.pro
new file mode 100644
index 0000000..6cdf91c
--- /dev/null
+++ b/room/runtime/proguard-rules.pro
@@ -0,0 +1 @@
+-keep public class * extends android.arch.persistence.room.RoomDatabase
diff --git a/room/runtime/src/androidTest/java/android/arch/persistence/room/migration/TableInfoTest.java b/room/runtime/src/androidTest/java/android/arch/persistence/room/migration/TableInfoTest.java
new file mode 100644
index 0000000..76effde
--- /dev/null
+++ b/room/runtime/src/androidTest/java/android/arch/persistence/room/migration/TableInfoTest.java
@@ -0,0 +1,219 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.arch.persistence.room.migration;
+
+
+import static org.hamcrest.CoreMatchers.equalTo;
+import static org.hamcrest.CoreMatchers.is;
+import static org.hamcrest.MatcherAssert.assertThat;
+
+import static java.util.Arrays.asList;
+import static java.util.Collections.singletonList;
+
+import android.arch.persistence.db.SupportSQLiteDatabase;
+import android.arch.persistence.db.SupportSQLiteOpenHelper;
+import android.arch.persistence.db.framework.FrameworkSQLiteOpenHelperFactory;
+import android.arch.persistence.room.util.TableInfo;
+import android.support.test.InstrumentationRegistry;
+import android.support.test.filters.SmallTest;
+import android.support.test.runner.AndroidJUnit4;
+
+import org.junit.After;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.io.IOException;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Map;
+import java.util.Set;
+
+@SuppressWarnings("ArraysAsListWithZeroOrOneArgument")
+@RunWith(AndroidJUnit4.class)
+@SmallTest
+public class TableInfoTest {
+    private SupportSQLiteDatabase mDb;
+
+    @Test
+    public void readSimple() {
+        mDb = createDatabase(
+                "CREATE TABLE foo (id INTEGER PRIMARY KEY AUTOINCREMENT,"
+                        + "name TEXT)");
+        TableInfo info = TableInfo.read(mDb, "foo");
+        assertThat(info, is(new TableInfo("foo",
+                toMap(new TableInfo.Column("id", "INTEGER", 1),
+                        new TableInfo.Column("name", "TEXT", 0)),
+                Collections.<TableInfo.ForeignKey>emptySet())));
+    }
+
+    @Test
+    public void multiplePrimaryKeys() {
+        mDb = createDatabase(
+                "CREATE TABLE foo (id INTEGER,"
+                        + "name TEXT, PRIMARY KEY(name, id))");
+        TableInfo info = TableInfo.read(mDb, "foo");
+        assertThat(info, is(new TableInfo("foo",
+                toMap(new TableInfo.Column("id", "INTEGER", 2),
+                        new TableInfo.Column("name", "TEXT", 1)),
+                Collections.<TableInfo.ForeignKey>emptySet())));
+    }
+
+    @Test
+    public void alteredTable() {
+        mDb = createDatabase(
+                "CREATE TABLE foo (id INTEGER,"
+                        + "name TEXT, PRIMARY KEY(name))");
+        mDb.execSQL("ALTER TABLE foo ADD COLUMN added REAL;");
+        TableInfo info = TableInfo.read(mDb, "foo");
+        assertThat(info, is(new TableInfo("foo",
+                toMap(new TableInfo.Column("id", "INTEGER", 0),
+                        new TableInfo.Column("name", "TEXT", 1),
+                        new TableInfo.Column("added", "REAL", 0)),
+                Collections.<TableInfo.ForeignKey>emptySet())));
+    }
+
+    @Test
+    public void nonNull() {
+        mDb = createDatabase(
+                "CREATE TABLE foo (name TEXT NOT NULL)");
+        TableInfo info = TableInfo.read(mDb, "foo");
+        assertThat(info, is(new TableInfo("foo",
+                toMap(new TableInfo.Column("name", "TEXT", 0)),
+                Collections.<TableInfo.ForeignKey>emptySet())));
+    }
+
+    @Test
+    public void defaultValue() {
+        mDb = createDatabase(
+                "CREATE TABLE foo (name TEXT DEFAULT blah)");
+        TableInfo info = TableInfo.read(mDb, "foo");
+        assertThat(info, is(new TableInfo(
+                "foo",
+                toMap(new TableInfo.Column("name", "TEXT", 0)),
+                Collections.<TableInfo.ForeignKey>emptySet())));
+    }
+
+    @Test
+    public void foreignKey() {
+        mDb = createDatabase(
+                "CREATE TABLE foo (name TEXT)",
+                "CREATE TABLE bar(barName TEXT, FOREIGN KEY(barName) REFERENCES foo(name))"
+        );
+        TableInfo info = TableInfo.read(mDb, "bar");
+        assertThat(info.foreignKeys.size(), is(1));
+        final TableInfo.ForeignKey foreignKey = info.foreignKeys.iterator().next();
+        assertThat(foreignKey.columnNames, is(singletonList("barName")));
+        assertThat(foreignKey.referenceColumnNames, is(singletonList("name")));
+        assertThat(foreignKey.onDelete, is("NO ACTION"));
+        assertThat(foreignKey.onUpdate, is("NO ACTION"));
+        assertThat(foreignKey.referenceTable, is("foo"));
+    }
+
+    @Test
+    public void multipleForeignKeys() {
+        mDb = createDatabase(
+                "CREATE TABLE foo (name TEXT, lastName TEXT)",
+                "CREATE TABLE foo2 (name TEXT, lastName TEXT)",
+                "CREATE TABLE bar(barName TEXT, barLastName TEXT, "
+                        + " FOREIGN KEY(barName) REFERENCES foo(name) ON UPDATE SET NULL,"
+                        + " FOREIGN KEY(barLastName) REFERENCES foo2(lastName) ON DELETE CASCADE)");
+        TableInfo info = TableInfo.read(mDb, "bar");
+        assertThat(info.foreignKeys.size(), is(2));
+        Set<TableInfo.ForeignKey> expected = new HashSet<>();
+        expected.add(new TableInfo.ForeignKey("foo2", // table
+                "CASCADE", // on delete
+                "NO ACTION", // on update
+                singletonList("barLastName"), // my
+                singletonList("lastName")) // ref
+        );
+        expected.add(new TableInfo.ForeignKey("foo", // table
+                "NO ACTION", // on delete
+                "SET NULL", // on update
+                singletonList("barName"), // mine
+                singletonList("name")/*ref*/));
+        assertThat(info.foreignKeys, equalTo(expected));
+    }
+
+    @Test
+    public void compositeForeignKey() {
+        mDb = createDatabase(
+                "CREATE TABLE foo (name TEXT, lastName TEXT)",
+                "CREATE TABLE bar(barName TEXT, barLastName TEXT, "
+                        + " FOREIGN KEY(barName, barLastName) REFERENCES foo(name, lastName)"
+                        + " ON UPDATE cascade ON DELETE RESTRICT)");
+        TableInfo info = TableInfo.read(mDb, "bar");
+        assertThat(info.foreignKeys.size(), is(1));
+        TableInfo.ForeignKey expected = new TableInfo.ForeignKey(
+                "foo", // table
+                "RESTRICT", // on delete
+                "CASCADE", // on update
+                asList("barName", "barLastName"), // my columns
+                asList("name", "lastName") // ref columns
+        );
+        assertThat(info.foreignKeys.iterator().next(), is(expected));
+    }
+
+    @Test
+    public void caseInsensitiveTypeName() {
+        mDb = createDatabase(
+                "CREATE TABLE foo (n integer)");
+        TableInfo info = TableInfo.read(mDb, "foo");
+        assertThat(info, is(new TableInfo(
+                "foo",
+                toMap(new TableInfo.Column("n", "INTEGER", 0)),
+                Collections.<TableInfo.ForeignKey>emptySet())));
+    }
+
+    private static Map<String, TableInfo.Column> toMap(TableInfo.Column... columns) {
+        Map<String, TableInfo.Column> result = new HashMap<>();
+        for (TableInfo.Column column : columns) {
+            result.put(column.name, column);
+        }
+        return result;
+    }
+
+    @After
+    public void closeDb() throws IOException {
+        if (mDb != null && mDb.isOpen()) {
+            mDb.close();
+        }
+    }
+
+    private static SupportSQLiteDatabase createDatabase(final String... queries) {
+        return new FrameworkSQLiteOpenHelperFactory().create(
+                SupportSQLiteOpenHelper.Configuration
+                        .builder(InstrumentationRegistry.getTargetContext())
+                        .name(null)
+                        .version(1)
+                        .callback(new SupportSQLiteOpenHelper.Callback() {
+                            @Override
+                            public void onCreate(SupportSQLiteDatabase db) {
+                                for (String query : queries) {
+                                    db.execSQL(query);
+                                }
+                            }
+
+                            @Override
+                            public void onUpgrade(SupportSQLiteDatabase db, int oldVersion,
+                                    int newVersion) {
+                                throw new IllegalStateException("should not be upgrading");
+                            }
+                        }).build()
+        ).getWritableDatabase();
+    }
+}
diff --git a/room/runtime/src/main/AndroidManifest.xml b/room/runtime/src/main/AndroidManifest.xml
new file mode 100644
index 0000000..54a5b36
--- /dev/null
+++ b/room/runtime/src/main/AndroidManifest.xml
@@ -0,0 +1,19 @@
+<!--
+  ~ Copyright (C) 2016 The Android Open Source Project
+  ~
+  ~ Licensed under the Apache License, Version 2.0 (the "License");
+  ~ you may not use this file except in compliance with the License.
+  ~ You may obtain a copy of the License at
+  ~
+  ~      http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing, software
+  ~ distributed under the License is distributed on an "AS IS" BASIS,
+  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  ~ See the License for the specific language governing permissions and
+  ~ limitations under the License.
+  -->
+
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+          package="android.arch.persistence.room">
+</manifest>
diff --git a/room/runtime/src/main/java/android/arch/persistence/room/DatabaseConfiguration.java b/room/runtime/src/main/java/android/arch/persistence/room/DatabaseConfiguration.java
new file mode 100644
index 0000000..e5ac1c0
--- /dev/null
+++ b/room/runtime/src/main/java/android/arch/persistence/room/DatabaseConfiguration.java
@@ -0,0 +1,79 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.arch.persistence.room;
+
+import android.arch.persistence.db.SupportSQLiteOpenHelper;
+import android.content.Context;
+import android.support.annotation.NonNull;
+import android.support.annotation.Nullable;
+import android.support.annotation.RestrictTo;
+
+/**
+ * Configuration class for a {@link RoomDatabase}.
+ */
+@SuppressWarnings("WeakerAccess")
+public class DatabaseConfiguration {
+    /**
+     * The factory to use to access the database.
+     */
+    @NonNull
+    public final SupportSQLiteOpenHelper.Factory sqliteOpenHelperFactory;
+    /**
+     * The context to use while connecting to the database.
+     */
+    @NonNull
+    public final Context context;
+    /**
+     * The name of the database file or null if it is an in-memory database.
+     */
+    @Nullable
+    public final String name;
+
+    /**
+     * Collection of available migrations.
+     */
+    @NonNull
+    public final RoomDatabase.MigrationContainer migrationContainer;
+
+    /**
+     * Whether Room should throw an exception for queries run on the main thread.
+     */
+    public final boolean allowMainThreadQueries;
+
+    /**
+     * Creates a database configuration with the given values.
+     *
+     * @param context The application context.
+     * @param name Name of the database, can be null if it is in memory.
+     * @param sqliteOpenHelperFactory The open helper factory to use.
+     * @param migrationContainer The migration container for migrations.
+     * @param allowMainThreadQueries Whether to allow main thread reads/writes or not.
+     *
+     * @hide
+     */
+    @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
+    public DatabaseConfiguration(@NonNull Context context, @Nullable String name,
+            @NonNull SupportSQLiteOpenHelper.Factory sqliteOpenHelperFactory,
+            @NonNull RoomDatabase.MigrationContainer migrationContainer,
+            boolean allowMainThreadQueries) {
+        this.sqliteOpenHelperFactory = sqliteOpenHelperFactory;
+        this.context = context;
+        this.name = name;
+        this.migrationContainer = migrationContainer;
+        this.allowMainThreadQueries = allowMainThreadQueries;
+    }
+}
diff --git a/room/runtime/src/main/java/android/arch/persistence/room/EntityDeletionOrUpdateAdapter.java b/room/runtime/src/main/java/android/arch/persistence/room/EntityDeletionOrUpdateAdapter.java
new file mode 100644
index 0000000..6f4aa68
--- /dev/null
+++ b/room/runtime/src/main/java/android/arch/persistence/room/EntityDeletionOrUpdateAdapter.java
@@ -0,0 +1,114 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.arch.persistence.room;
+
+import android.arch.persistence.db.SupportSQLiteStatement;
+import android.support.annotation.RestrictTo;
+
+/**
+ * Implementations of this class knows how to delete or update a particular entity.
+ * <p>
+ * This is an internal library class and all of its implementations are auto-generated.
+ *
+ * @param <T> The type parameter of the entity to be deleted
+ * @hide
+ */
+@RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
+@SuppressWarnings({"WeakerAccess", "unused"})
+public abstract class EntityDeletionOrUpdateAdapter<T> extends SharedSQLiteStatement {
+    /**
+     * Creates a DeletionOrUpdateAdapter that can delete or update the entity type T on the given
+     * database.
+     *
+     * @param database The database to delete / update the item in.
+     */
+    public EntityDeletionOrUpdateAdapter(RoomDatabase database) {
+        super(database);
+    }
+
+    /**
+     * Create the deletion or update query
+     *
+     * @return An SQL query that can delete or update instances of T.
+     */
+    protected abstract String createQuery();
+
+    /**
+     * Binds the entity into the given statement.
+     *
+     * @param statement The SQLite statement that prepared for the query returned from
+     *                  createQuery.
+     * @param entity    The entity of type T.
+     */
+    protected abstract void bind(SupportSQLiteStatement statement, T entity);
+
+    /**
+     * Deletes or updates the given entities in the database and returns the affected row count.
+     *
+     * @param entity The entity to delete or update
+     * @return The number of affected rows
+     */
+    public final int handle(T entity) {
+        final SupportSQLiteStatement stmt = acquire();
+        try {
+            bind(stmt, entity);
+            return stmt.executeUpdateDelete();
+        } finally {
+            release(stmt);
+        }
+    }
+
+    /**
+     * Deletes or updates the given entities in the database and returns the affected row count.
+     *
+     * @param entities Entities to delete or update
+     * @return The number of affected rows
+     */
+    public final int handleMultiple(Iterable<T> entities) {
+        final SupportSQLiteStatement stmt = acquire();
+        try {
+            int total = 0;
+            for (T entity : entities) {
+                bind(stmt, entity);
+                total += stmt.executeUpdateDelete();
+            }
+            return total;
+        } finally {
+            release(stmt);
+        }
+    }
+
+    /**
+     * Deletes or updates the given entities in the database and returns the affected row count.
+     *
+     * @param entities Entities to delete or update
+     * @return The number of affected rows
+     */
+    public final int handleMultiple(T[] entities) {
+        final SupportSQLiteStatement stmt = acquire();
+        try {
+            int total = 0;
+            for (T entity : entities) {
+                bind(stmt, entity);
+                total += stmt.executeUpdateDelete();
+            }
+            return total;
+        } finally {
+            release(stmt);
+        }
+    }
+}
diff --git a/room/runtime/src/main/java/android/arch/persistence/room/EntityInsertionAdapter.java b/room/runtime/src/main/java/android/arch/persistence/room/EntityInsertionAdapter.java
new file mode 100644
index 0000000..6cfa332
--- /dev/null
+++ b/room/runtime/src/main/java/android/arch/persistence/room/EntityInsertionAdapter.java
@@ -0,0 +1,251 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.arch.persistence.room;
+
+import android.arch.persistence.db.SupportSQLiteStatement;
+import android.support.annotation.RestrictTo;
+
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.List;
+
+/**
+ * Implementations of this class knows how to insert a particular entity.
+ * <p>
+ * This is an internal library class and all of its implementations are auto-generated.
+ *
+ * @param <T> The type parameter of the entity to be inserted
+ * @hide
+ */
+@SuppressWarnings({"WeakerAccess", "unused"})
+@RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
+public abstract class EntityInsertionAdapter<T> extends SharedSQLiteStatement {
+    /**
+     * Creates an InsertionAdapter that can insert the entity type T into the given database.
+     *
+     * @param database The database to insert into.
+     */
+    public EntityInsertionAdapter(RoomDatabase database) {
+        super(database);
+    }
+
+    /**
+     * Binds the entity into the given statement.
+     *
+     * @param statement The SQLite statement that prepared for the query returned from
+     *                  createInsertQuery.
+     * @param entity    The entity of type T.
+     */
+    protected abstract void bind(SupportSQLiteStatement statement, T entity);
+
+    /**
+     * Inserts the entity into the database.
+     *
+     * @param entity The entity to insert
+     */
+    public final void insert(T entity) {
+        final SupportSQLiteStatement stmt = acquire();
+        try {
+            bind(stmt, entity);
+            stmt.executeInsert();
+        } finally {
+            release(stmt);
+        }
+    }
+
+    /**
+     * Inserts the given entities into the database.
+     *
+     * @param entities Entities to insert
+     */
+    public final void insert(T[] entities) {
+        final SupportSQLiteStatement stmt = acquire();
+        try {
+            for (T entity : entities) {
+                bind(stmt, entity);
+                stmt.executeInsert();
+            }
+        } finally {
+            release(stmt);
+        }
+    }
+
+    /**
+     * Inserts the given entities into the database.
+     *
+     * @param entities Entities to insert
+     */
+    public final void insert(Iterable<T> entities) {
+        final SupportSQLiteStatement stmt = acquire();
+        try {
+            for (T entity : entities) {
+                bind(stmt, entity);
+                stmt.executeInsert();
+            }
+        } finally {
+            release(stmt);
+        }
+    }
+
+    /**
+     * Inserts the given entity into the database and returns the row id.
+     *
+     * @param entity The entity to insert
+     * @return The SQLite row id
+     */
+    public final long insertAndReturnId(T entity) {
+        final SupportSQLiteStatement stmt = acquire();
+        try {
+            bind(stmt, entity);
+            return stmt.executeInsert();
+        } finally {
+            release(stmt);
+        }
+    }
+
+    /**
+     * Inserts the given entities into the database and returns the row ids.
+     *
+     * @param entities Entities to insert
+     * @return The SQLite row ids
+     */
+    public final long[] insertAndReturnIdsArray(Collection<T> entities) {
+        final SupportSQLiteStatement stmt = acquire();
+        try {
+            final long[] result = new long[entities.size()];
+            int index = 0;
+            for (T entity : entities) {
+                bind(stmt, entity);
+                result[index] = stmt.executeInsert();
+                index++;
+            }
+            return result;
+        } finally {
+            release(stmt);
+        }
+    }
+
+    /**
+     * Inserts the given entities into the database and returns the row ids.
+     *
+     * @param entities Entities to insert
+     * @return The SQLite row ids
+     */
+    public final long[] insertAndReturnIdsArray(T[] entities) {
+        final SupportSQLiteStatement stmt = acquire();
+        try {
+            final long[] result = new long[entities.length];
+            int index = 0;
+            for (T entity : entities) {
+                bind(stmt, entity);
+                result[index] = stmt.executeInsert();
+                index++;
+            }
+            return result;
+        } finally {
+            release(stmt);
+        }
+    }
+
+    /**
+     * Inserts the given entities into the database and returns the row ids.
+     *
+     * @param entities Entities to insert
+     * @return The SQLite row ids
+     */
+    public final Long[] insertAndReturnIdsArrayBox(Collection<T> entities) {
+        final SupportSQLiteStatement stmt = acquire();
+        try {
+            final Long[] result = new Long[entities.size()];
+            int index = 0;
+            for (T entity : entities) {
+                bind(stmt, entity);
+                result[index] = stmt.executeInsert();
+                index++;
+            }
+            return result;
+        } finally {
+            release(stmt);
+        }
+    }
+
+    /**
+     * Inserts the given entities into the database and returns the row ids.
+     *
+     * @param entities Entities to insert
+     * @return The SQLite row ids
+     */
+    public final Long[] insertAndReturnIdsArrayBox(T[] entities) {
+        final SupportSQLiteStatement stmt = acquire();
+        try {
+            final Long[] result = new Long[entities.length];
+            int index = 0;
+            for (T entity : entities) {
+                bind(stmt, entity);
+                result[index] = stmt.executeInsert();
+                index++;
+            }
+            return result;
+        } finally {
+            release(stmt);
+        }
+    }
+
+    /**
+     * Inserts the given entities into the database and returns the row ids.
+     *
+     * @param entities Entities to insert
+     * @return The SQLite row ids
+     */
+    public final List<Long> insertAndReturnIdsList(T[] entities) {
+        final SupportSQLiteStatement stmt = acquire();
+        try {
+            final List<Long> result = new ArrayList<>(entities.length);
+            int index = 0;
+            for (T entity : entities) {
+                bind(stmt, entity);
+                result.add(index, stmt.executeInsert());
+                index++;
+            }
+            return result;
+        } finally {
+            release(stmt);
+        }
+    }
+
+    /**
+     * Inserts the given entities into the database and returns the row ids.
+     *
+     * @param entities Entities to insert
+     * @return The SQLite row ids
+     */
+    public final List<Long> insertAndReturnIdsList(Collection<T> entities) {
+        final SupportSQLiteStatement stmt = acquire();
+        try {
+            final List<Long> result = new ArrayList<>(entities.size());
+            int index = 0;
+            for (T entity : entities) {
+                bind(stmt, entity);
+                result.add(index, stmt.executeInsert());
+                index++;
+            }
+            return result;
+        } finally {
+            release(stmt);
+        }
+    }
+}
diff --git a/room/runtime/src/main/java/android/arch/persistence/room/InvalidationTracker.java b/room/runtime/src/main/java/android/arch/persistence/room/InvalidationTracker.java
new file mode 100644
index 0000000..edfda0a
--- /dev/null
+++ b/room/runtime/src/main/java/android/arch/persistence/room/InvalidationTracker.java
@@ -0,0 +1,639 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.arch.persistence.room;
+
+import android.arch.core.executor.AppToolkitTaskExecutor;
+import android.arch.core.internal.SafeIterableMap;
+import android.arch.persistence.db.SupportSQLiteDatabase;
+import android.arch.persistence.db.SupportSQLiteStatement;
+import android.database.Cursor;
+import android.database.sqlite.SQLiteException;
+import android.support.annotation.NonNull;
+import android.support.annotation.Nullable;
+import android.support.annotation.RestrictTo;
+import android.support.annotation.VisibleForTesting;
+import android.support.v4.util.ArrayMap;
+import android.support.v4.util.ArraySet;
+import android.util.Log;
+
+import java.lang.ref.WeakReference;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.Locale;
+import java.util.Map;
+import java.util.Set;
+import java.util.concurrent.atomic.AtomicBoolean;
+
+/**
+ * InvalidationTracker keeps a list of tables modified by queries and notifies its callbacks about
+ * these tables.
+ */
+// We create an in memory table with (version, table_id) where version is an auto-increment primary
+// key and a table_id (hardcoded int from initialization).
+// ObservedTableTracker tracks list of tables we should be watching (e.g. adding triggers for).
+// Before each beginTransaction, RoomDatabase invokes InvalidationTracker to sync trigger states.
+// After each endTransaction, RoomDatabase invokes InvalidationTracker to refresh invalidated
+// tables.
+// Each update on one of the observed tables triggers an insertion into this table, hence a
+// new version.
+// Unfortunately, we cannot override the previous row because sqlite uses the conflict resolution
+// of the outer query (the thing that triggered us) so we do a cleanup as we sync instead of letting
+// SQLite override the rows.
+// https://sqlite.org/lang_createtrigger.html:  An ON CONFLICT clause may be specified as part of an
+// UPDATE or INSERT action within the body of the trigger. However if an ON CONFLICT clause is
+// specified as part of the statement causing the trigger to fire, then conflict handling policy of
+// the outer statement is used instead.
+public class InvalidationTracker {
+
+    private static final String[] TRIGGERS = new String[]{"UPDATE", "DELETE", "INSERT"};
+
+    private static final String UPDATE_TABLE_NAME = "room_table_modification_log";
+
+    private static final String VERSION_COLUMN_NAME = "version";
+
+    private static final String TABLE_ID_COLUMN_NAME = "table_id";
+
+    private static final String CREATE_VERSION_TABLE_SQL = "CREATE TEMP TABLE " + UPDATE_TABLE_NAME
+            + "(" + VERSION_COLUMN_NAME
+            + " INTEGER PRIMARY KEY AUTOINCREMENT, "
+            + TABLE_ID_COLUMN_NAME
+            + " INTEGER)";
+
+    @VisibleForTesting
+    static final String CLEANUP_SQL = "DELETE FROM " + UPDATE_TABLE_NAME
+            + " WHERE " + VERSION_COLUMN_NAME + " NOT IN( SELECT MAX("
+            + VERSION_COLUMN_NAME + ") FROM " + UPDATE_TABLE_NAME
+            + " GROUP BY " + TABLE_ID_COLUMN_NAME + ")";
+
+    @VisibleForTesting
+    // We always clean before selecting so it is unlikely to have the same row twice and if we
+    // do, it is not a big deal, just more data in the cursor.
+    static final String SELECT_UPDATED_TABLES_SQL = "SELECT * FROM " + UPDATE_TABLE_NAME
+            + " WHERE " + VERSION_COLUMN_NAME
+            + "  > ? ORDER BY " + VERSION_COLUMN_NAME + " ASC;";
+
+    @NonNull
+    @VisibleForTesting
+    ArrayMap<String, Integer> mTableIdLookup;
+    private String[] mTableNames;
+
+    @NonNull
+    @VisibleForTesting
+    long[] mTableVersions;
+
+    private Object[] mQueryArgs = new Object[1];
+
+    // max id in the last syc
+    private long mMaxVersion = -1;
+
+    private final RoomDatabase mDatabase;
+
+    AtomicBoolean mPendingRefresh = new AtomicBoolean(false);
+
+    private volatile boolean mInitialized = false;
+
+    private volatile SupportSQLiteStatement mCleanupStatement;
+
+    private ObservedTableTracker mObservedTableTracker;
+
+    // should be accessed with synchronization only.
+    @VisibleForTesting
+    final SafeIterableMap<Observer, ObserverWrapper> mObserverMap = new SafeIterableMap<>();
+
+    /**
+     * Used by the generated code.
+     *
+     * @hide
+     */
+    @SuppressWarnings("WeakerAccess")
+    @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
+    public InvalidationTracker(RoomDatabase database, String... tableNames) {
+        mDatabase = database;
+        mObservedTableTracker = new ObservedTableTracker(tableNames.length);
+        mTableIdLookup = new ArrayMap<>();
+        final int size = tableNames.length;
+        mTableNames = new String[size];
+        for (int id = 0; id < size; id++) {
+            final String tableName = tableNames[id].toLowerCase(Locale.US);
+            mTableIdLookup.put(tableName, id);
+            mTableNames[id] = tableName;
+        }
+        mTableVersions = new long[tableNames.length];
+        Arrays.fill(mTableVersions, 0);
+    }
+
+    /**
+     * Internal method to initialize table tracking.
+     * <p>
+     * You should never call this method, it is called by the generated code.
+     */
+    void internalInit(SupportSQLiteDatabase database) {
+        synchronized (this) {
+            if (mInitialized) {
+                Log.e(Room.LOG_TAG, "Invalidation tracker is initialized twice :/.");
+                return;
+            }
+
+            database.beginTransaction();
+            try {
+                database.execSQL("PRAGMA temp_store = MEMORY;");
+                database.execSQL("PRAGMA recursive_triggers='ON';");
+                database.execSQL(CREATE_VERSION_TABLE_SQL);
+                database.setTransactionSuccessful();
+            } finally {
+                database.endTransaction();
+            }
+            mCleanupStatement = database.compileStatement(CLEANUP_SQL);
+            mInitialized = true;
+        }
+    }
+
+    private static void appendTriggerName(StringBuilder builder, String tableName,
+            String triggerType) {
+        builder.append("room_table_modification_trigger_")
+                .append(tableName)
+                .append("_")
+                .append(triggerType);
+    }
+
+    private void stopTrackingTable(SupportSQLiteDatabase writableDb, int tableId) {
+        final String tableName = mTableNames[tableId];
+        StringBuilder stringBuilder = new StringBuilder();
+        for (String trigger : TRIGGERS) {
+            stringBuilder.setLength(0);
+            stringBuilder.append("DROP TRIGGER IF EXISTS ");
+            appendTriggerName(stringBuilder, tableName, trigger);
+            writableDb.execSQL(stringBuilder.toString());
+        }
+    }
+
+    private void startTrackingTable(SupportSQLiteDatabase writableDb, int tableId) {
+        final String tableName = mTableNames[tableId];
+        StringBuilder stringBuilder = new StringBuilder();
+        for (String trigger : TRIGGERS) {
+            stringBuilder.setLength(0);
+            stringBuilder.append("CREATE TEMP TRIGGER IF NOT EXISTS ");
+            appendTriggerName(stringBuilder, tableName, trigger);
+            stringBuilder.append(" AFTER ")
+                    .append(trigger)
+                    .append(" ON ")
+                    .append(tableName)
+                    .append(" BEGIN INSERT OR REPLACE INTO ")
+                    .append(UPDATE_TABLE_NAME)
+                    .append(" VALUES(null, ")
+                    .append(tableId)
+                    .append("); END");
+            writableDb.execSQL(stringBuilder.toString());
+        }
+    }
+
+    /**
+     * Adds the given observer to the observers list and it will be notified if any table it
+     * observes changes.
+     * <p>
+     * Database changes are pulled on another thread so in some race conditions, the observer might
+     * be invoked for changes that were done before it is added.
+     * <p>
+     * If the observer already exists, this is a no-op call.
+     * <p>
+     * If one of the tables in the Observer does not exist in the database, this method throws an
+     * {@link IllegalArgumentException}.
+     *
+     * @param observer The observer which listens the database for changes.
+     */
+    public void addObserver(Observer observer) {
+        final String[] tableNames = observer.mTables;
+        int[] tableIds = new int[tableNames.length];
+        final int size = tableNames.length;
+        long[] versions = new long[tableNames.length];
+
+        // TODO sync versions ?
+        for (int i = 0; i < size; i++) {
+            Integer tableId = mTableIdLookup.get(tableNames[i].toLowerCase(Locale.US));
+            if (tableId == null) {
+                throw new IllegalArgumentException("There is no table with name " + tableNames[i]);
+            }
+            tableIds[i] = tableId;
+            versions[i] = mMaxVersion;
+        }
+        ObserverWrapper wrapper = new ObserverWrapper(observer, tableIds, tableNames, versions);
+        ObserverWrapper currentObserver;
+        synchronized (mObserverMap) {
+            currentObserver = mObserverMap.putIfAbsent(observer, wrapper);
+        }
+        if (currentObserver == null && mObservedTableTracker.onAdded(tableIds)) {
+            AppToolkitTaskExecutor.getInstance().executeOnDiskIO(mSyncTriggers);
+        }
+    }
+
+    /**
+     * Adds an observer but keeps a weak reference back to it.
+     * <p>
+     * Note that you cannot remove this observer once added. It will be automatically removed
+     * when the observer is GC'ed.
+     *
+     * @param observer The observer to which InvalidationTracker will keep a weak reference.
+     * @hide
+     */
+    @SuppressWarnings("unused")
+    @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
+    public void addWeakObserver(Observer observer) {
+        addObserver(new WeakObserver(this, observer));
+    }
+
+    /**
+     * Removes the observer from the observers list.
+     *
+     * @param observer The observer to remove.
+     */
+    @SuppressWarnings("WeakerAccess")
+    public void removeObserver(final Observer observer) {
+        ObserverWrapper wrapper;
+        synchronized (mObserverMap) {
+            wrapper = mObserverMap.remove(observer);
+        }
+        if (wrapper != null && mObservedTableTracker.onRemoved(wrapper.mTableIds)) {
+            AppToolkitTaskExecutor.getInstance().executeOnDiskIO(mSyncTriggers);
+        }
+    }
+
+    private Runnable mSyncTriggers = new Runnable() {
+        @Override
+        public void run() {
+            if (mDatabase.inTransaction()) {
+                // we won't run this inside another transaction.
+                return;
+            }
+            if (!ensureInitialization()) {
+                return;
+            }
+            try {
+                // This method runs in a while loop because while changes are synced to db, another
+                // runnable may be skipped. If we cause it to skip, we need to do its work.
+                while (true) {
+                    // there is a potential race condition where another mSyncTriggers runnable
+                    // can start running right after we get the tables list to sync.
+                    final int[] tablesToSync = mObservedTableTracker.getTablesToSync();
+                    if (tablesToSync == null) {
+                        return;
+                    }
+                    final int limit = tablesToSync.length;
+                    final SupportSQLiteDatabase writableDatabase = mDatabase.getOpenHelper()
+                            .getWritableDatabase();
+                    try {
+                        writableDatabase.beginTransaction();
+                        for (int tableId = 0; tableId < limit; tableId++) {
+                            switch (tablesToSync[tableId]) {
+                                case ObservedTableTracker.ADD:
+                                    startTrackingTable(writableDatabase, tableId);
+                                    break;
+                                case ObservedTableTracker.REMOVE:
+                                    stopTrackingTable(writableDatabase, tableId);
+                                    break;
+                            }
+                        }
+                        writableDatabase.setTransactionSuccessful();
+                    } finally {
+                        writableDatabase.endTransaction();
+                    }
+                    mObservedTableTracker.onSyncCompleted();
+                }
+            } catch (IllegalStateException | SQLiteException exception) {
+                // may happen if db is closed. just log.
+                Log.e(Room.LOG_TAG, "Cannot run invalidation tracker. Is the db closed?",
+                        exception);
+            }
+        }
+    };
+
+    private boolean ensureInitialization() {
+        if (!mDatabase.isOpen()) {
+            return false;
+        }
+        if (!mInitialized) {
+            // trigger initialization
+            mDatabase.getOpenHelper().getWritableDatabase();
+        }
+        if (!mInitialized) {
+            Log.e(Room.LOG_TAG, "database is not initialized even though it is open");
+            return false;
+        }
+        return true;
+    }
+
+    @VisibleForTesting
+    Runnable mRefreshRunnable = new Runnable() {
+        @Override
+        public void run() {
+            if (!ensureInitialization()) {
+                return;
+            }
+            if (mDatabase.inTransaction()
+                    || !mPendingRefresh.compareAndSet(true, false)) {
+                // no pending refresh
+                return;
+            }
+            boolean hasUpdatedTable = false;
+            try {
+                mCleanupStatement.executeUpdateDelete();
+                mQueryArgs[0] = mMaxVersion;
+                Cursor cursor = mDatabase.query(SELECT_UPDATED_TABLES_SQL, mQueryArgs);
+                //noinspection TryFinallyCanBeTryWithResources
+                try {
+                    while (cursor.moveToNext()) {
+                        final long version = cursor.getLong(0);
+                        final int tableId = cursor.getInt(1);
+
+                        mTableVersions[tableId] = version;
+                        hasUpdatedTable = true;
+                        // result is ordered so we can safely do this assignment
+                        mMaxVersion = version;
+                    }
+                } finally {
+                    cursor.close();
+                }
+            } catch (IllegalStateException | SQLiteException exception) {
+                // may happen if db is closed. just log.
+                Log.e(Room.LOG_TAG, "Cannot run invalidation tracker. Is the db closed?",
+                        exception);
+            }
+            if (hasUpdatedTable) {
+                synchronized (mObserverMap) {
+                    for (Map.Entry<Observer, ObserverWrapper> entry : mObserverMap) {
+                        entry.getValue().checkForInvalidation(mTableVersions);
+                    }
+                }
+            }
+        }
+    };
+
+    /**
+     * Enqueues a task to refresh the list of updated tables.
+     * <p>
+     * This method is automatically called when {@link RoomDatabase#endTransaction()} is called but
+     * if you have another connection to the database or directly use {@link
+     * SupportSQLiteDatabase}, you may need to call this manually.
+     */
+    @SuppressWarnings("WeakerAccess")
+    public void refreshVersionsAsync() {
+        // TODO we should consider doing this sync instead of async.
+        if (mPendingRefresh.compareAndSet(false, true)) {
+            AppToolkitTaskExecutor.getInstance().executeOnDiskIO(mRefreshRunnable);
+        }
+    }
+
+    /**
+     * Called by RoomDatabase before each beginTransaction call.
+     * <p>
+     * It is important that pending trigger changes are applied to the database before any query
+     * runs. Otherwise, we may miss some changes.
+     * <p>
+     * This api should eventually be public.
+     */
+    void syncTriggers() {
+        mSyncTriggers.run();
+    }
+
+    /**
+     * Wraps an observer and keeps the table information.
+     * <p>
+     * Internally table ids are used which may change from database to database so the table
+     * related information is kept here rather than in the Observer.
+     */
+    @SuppressWarnings("WeakerAccess")
+    static class ObserverWrapper {
+        final int[] mTableIds;
+        private final String[] mTableNames;
+        private final long[] mVersions;
+        final Observer mObserver;
+        private final Set<String> mSingleTableSet;
+
+        ObserverWrapper(Observer observer, int[] tableIds, String[] tableNames, long[] versions) {
+            mObserver = observer;
+            mTableIds = tableIds;
+            mTableNames = tableNames;
+            mVersions = versions;
+            if (tableIds.length == 1) {
+                ArraySet<String> set = new ArraySet<>();
+                set.add(mTableNames[0]);
+                mSingleTableSet = Collections.unmodifiableSet(set);
+            } else {
+                mSingleTableSet = null;
+            }
+        }
+
+        void checkForInvalidation(long[] versions) {
+            Set<String> invalidatedTables = null;
+            final int size = mTableIds.length;
+            for (int index = 0; index < size; index++) {
+                final int tableId = mTableIds[index];
+                final long newVersion = versions[tableId];
+                final long currentVersion = mVersions[index];
+                if (currentVersion < newVersion) {
+                    mVersions[index] = newVersion;
+                    if (size == 1) {
+                        // Optimization for a single-table observer
+                        invalidatedTables = mSingleTableSet;
+                    } else {
+                        if (invalidatedTables == null) {
+                            invalidatedTables = new ArraySet<>(size);
+                        }
+                        invalidatedTables.add(mTableNames[index]);
+                    }
+                }
+            }
+            if (invalidatedTables != null) {
+                mObserver.onInvalidated(invalidatedTables);
+            }
+        }
+    }
+
+    /**
+     * An observer that can listen for changes in the database.
+     */
+    public abstract static class Observer {
+        final String[] mTables;
+
+        /**
+         * Observes the given list of tables.
+         *
+         * @param firstTable The table name
+         * @param rest       More table names
+         */
+        @SuppressWarnings("unused")
+        protected Observer(@NonNull String firstTable, String... rest) {
+            mTables = Arrays.copyOf(rest, rest.length + 1);
+            mTables[rest.length] = firstTable;
+        }
+
+        /**
+         * Observes the given list of tables.
+         *
+         * @param tables The list of tables to observe for changes.
+         */
+        public Observer(@NonNull String[] tables) {
+            // copy tables in case user modifies them afterwards
+            mTables = Arrays.copyOf(tables, tables.length);
+        }
+
+        /**
+         * Called when one of the observed tables is invalidated in the database.
+         *
+         * @param tables A set of invalidated tables. This is useful when the observer targets
+         *               multiple tables and want to know which table is invalidated.
+         */
+        public abstract void onInvalidated(@NonNull Set<String> tables);
+    }
+
+
+    /**
+     * Keeps a list of tables we should observe. Invalidation tracker lazily syncs this list w/
+     * triggers in the database.
+     * <p>
+     * This class is thread safe
+     */
+    static class ObservedTableTracker {
+        static final int NO_OP = 0; // don't change trigger state for this table
+        static final int ADD = 1; // add triggers for this table
+        static final int REMOVE = 2; // remove triggers for this table
+
+        // number of observers per table
+        final long[] mTableObservers;
+        // trigger state for each table at last sync
+        // this field is updated when syncAndGet is called.
+        final boolean[] mTriggerStates;
+        // when sync is called, this field is returned. It includes actions as ADD, REMOVE, NO_OP
+        final int[] mTriggerStateChanges;
+
+        boolean mNeedsSync;
+
+        /**
+         * After we return non-null value from getTablesToSync, we expect a onSyncCompleted before
+         * returning any non-null value from getTablesToSync.
+         * This allows us to workaround any multi-threaded state syncing issues.
+         */
+        boolean mPendingSync;
+
+        ObservedTableTracker(int tableCount) {
+            mTableObservers = new long[tableCount];
+            mTriggerStates = new boolean[tableCount];
+            mTriggerStateChanges = new int[tableCount];
+            Arrays.fill(mTableObservers, 0);
+            Arrays.fill(mTriggerStates, false);
+        }
+
+        /**
+         * @return true if # of triggers is affected.
+         */
+        boolean onAdded(int... tableIds) {
+            boolean needTriggerSync = false;
+            synchronized (this) {
+                for (int tableId : tableIds) {
+                    final long prevObserverCount = mTableObservers[tableId];
+                    mTableObservers[tableId] = prevObserverCount + 1;
+                    if (prevObserverCount == 0) {
+                        mNeedsSync = true;
+                        needTriggerSync = true;
+                    }
+                }
+            }
+            return needTriggerSync;
+        }
+
+        /**
+         * @return true if # of triggers is affected.
+         */
+        boolean onRemoved(int... tableIds) {
+            boolean needTriggerSync = false;
+            synchronized (this) {
+                for (int tableId : tableIds) {
+                    final long prevObserverCount = mTableObservers[tableId];
+                    mTableObservers[tableId] = prevObserverCount - 1;
+                    if (prevObserverCount == 1) {
+                        mNeedsSync = true;
+                        needTriggerSync = true;
+                    }
+                }
+            }
+            return needTriggerSync;
+        }
+
+        /**
+         * If this returns non-null, you must call onSyncCompleted.
+         *
+         * @return int[] An int array where the index for each tableId has the action for that
+         * table.
+         */
+        @Nullable
+        int[] getTablesToSync() {
+            synchronized (this) {
+                if (!mNeedsSync || mPendingSync) {
+                    return null;
+                }
+                final int tableCount = mTableObservers.length;
+                for (int i = 0; i < tableCount; i++) {
+                    final boolean newState = mTableObservers[i] > 0;
+                    if (newState != mTriggerStates[i]) {
+                        mTriggerStateChanges[i] = newState ? ADD : REMOVE;
+                    } else {
+                        mTriggerStateChanges[i] = NO_OP;
+                    }
+                    mTriggerStates[i] = newState;
+                }
+                mPendingSync = true;
+                mNeedsSync = false;
+                return mTriggerStateChanges;
+            }
+        }
+
+        /**
+         * if getTablesToSync returned non-null, the called should call onSyncCompleted once it
+         * is done.
+         */
+        void onSyncCompleted() {
+            synchronized (this) {
+                mPendingSync = false;
+            }
+        }
+    }
+
+    /**
+     * An Observer wrapper that keeps a weak reference to the given object.
+     * <p>
+     * This class with automatically unsubscribe when the wrapped observer goes out of memory.
+     */
+    static class WeakObserver extends Observer {
+        final InvalidationTracker mTracker;
+        final WeakReference<Observer> mDelegateRef;
+
+        WeakObserver(InvalidationTracker tracker, Observer delegate) {
+            super(delegate.mTables);
+            mTracker = tracker;
+            mDelegateRef = new WeakReference<>(delegate);
+        }
+
+        @Override
+        public void onInvalidated(@NonNull Set<String> tables) {
+            final Observer observer = mDelegateRef.get();
+            if (observer == null) {
+                mTracker.removeObserver(this);
+            } else {
+                observer.onInvalidated(tables);
+            }
+        }
+    }
+}
diff --git a/room/runtime/src/main/java/android/arch/persistence/room/Room.java b/room/runtime/src/main/java/android/arch/persistence/room/Room.java
new file mode 100644
index 0000000..80e95a7
--- /dev/null
+++ b/room/runtime/src/main/java/android/arch/persistence/room/Room.java
@@ -0,0 +1,99 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.arch.persistence.room;
+
+import android.content.Context;
+import android.support.annotation.NonNull;
+
+/**
+ * Utility class for Room.
+ */
+@SuppressWarnings("unused")
+public class Room {
+    static final String LOG_TAG = "ROOM";
+    /**
+     * The master table where room keeps its metadata information.
+     */
+    public static final String MASTER_TABLE_NAME = RoomMasterTable.TABLE_NAME;
+    private static final String CURSOR_CONV_SUFFIX = "_CursorConverter";
+
+    /**
+     * Creates a RoomDatabase.Builder for a persistent database. Once a database is built, you
+     * should keep a reference to it and re-use it.
+     *
+     * @param context The context for the database. This is usually the Application context.
+     * @param klass   The abstract class which is annotated with {@link Database} and extends
+     *                {@link RoomDatabase}.
+     * @param name    The name of the database file.
+     * @param <T>     The type of the database class.
+     * @return A {@code RoomDatabaseBuilder<T>} which you can use to create the database.
+     */
+    @SuppressWarnings("WeakerAccess")
+    public static <T extends RoomDatabase> RoomDatabase.Builder<T> databaseBuilder(
+            @NonNull Context context, @NonNull Class<T> klass, @NonNull String name) {
+        //noinspection ConstantConditions
+        if (name == null || name.trim().length() == 0) {
+            throw new IllegalArgumentException("Cannot create a database with null or empty name."
+                    + " If you are trying to create an in memory database, use Room"
+                    + ".inMemoryDatabaseBuilder");
+        }
+        return new RoomDatabase.Builder<>(context, klass, name);
+    }
+
+    /**
+     * Creates a RoomDatabase.Builder for an in memory database. Information stored in an in memory
+     * database disappears when the process is killed.
+     * Once a database is built, you should keep a reference to it and re-use it.
+     *
+     * @param context The context for the database. This is usually the Application context.
+     * @param klass   The abstract class which is annotated with {@link Database} and extends
+     *                {@link RoomDatabase}.
+     * @param <T>     The type of the database class.
+     * @return A {@code RoomDatabaseBuilder<T>} which you can use to create the database.
+     */
+    public static <T extends RoomDatabase> RoomDatabase.Builder<T> inMemoryDatabaseBuilder(
+            @NonNull Context context, @NonNull Class<T> klass) {
+        return new RoomDatabase.Builder<>(context, klass, null);
+    }
+
+    @NonNull
+    static <T, C> T getGeneratedImplementation(Class<C> klass, String suffix) {
+        final String fullPackage = klass.getPackage().getName();
+        String name = klass.getCanonicalName();
+        final String postPackageName = fullPackage.isEmpty()
+                ? name
+                : (name.substring(fullPackage.length() + 1));
+        final String implName = postPackageName.replace('.', '_') + suffix;
+        //noinspection TryWithIdenticalCatches
+        try {
+
+            @SuppressWarnings("unchecked")
+            final Class<T> aClass = (Class<T>) Class.forName(
+                    fullPackage.isEmpty() ? implName : fullPackage + "." + implName);
+            return aClass.newInstance();
+        } catch (ClassNotFoundException e) {
+            throw new RuntimeException("cannot find implementation for "
+                    + klass.getCanonicalName() + ". " + implName + " does not exist");
+        } catch (IllegalAccessException e) {
+            throw new RuntimeException("Cannot access the constructor"
+                    + klass.getCanonicalName());
+        } catch (InstantiationException e) {
+            throw new RuntimeException("Failed to create an instance of "
+                    + klass.getCanonicalName());
+        }
+    }
+}
diff --git a/room/runtime/src/main/java/android/arch/persistence/room/RoomDatabase.java b/room/runtime/src/main/java/android/arch/persistence/room/RoomDatabase.java
new file mode 100644
index 0000000..7846999
--- /dev/null
+++ b/room/runtime/src/main/java/android/arch/persistence/room/RoomDatabase.java
@@ -0,0 +1,478 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.arch.persistence.room;
+
+import android.arch.core.executor.AppToolkitTaskExecutor;
+import android.arch.persistence.db.SimpleSQLiteQuery;
+import android.arch.persistence.db.SupportSQLiteDatabase;
+import android.arch.persistence.db.SupportSQLiteOpenHelper;
+import android.arch.persistence.db.SupportSQLiteQuery;
+import android.arch.persistence.db.SupportSQLiteStatement;
+import android.arch.persistence.db.framework.FrameworkSQLiteOpenHelperFactory;
+import android.arch.persistence.room.migration.Migration;
+import android.content.Context;
+import android.database.Cursor;
+import android.support.annotation.CallSuper;
+import android.support.annotation.NonNull;
+import android.support.annotation.Nullable;
+import android.support.annotation.RestrictTo;
+import android.support.v4.util.SparseArrayCompat;
+import android.util.Log;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+import java.util.concurrent.Callable;
+
+/**
+ * Base class for all Room databases. All classes that are annotated with {@link Database} must
+ * extend this class.
+ * <p>
+ * RoomDatabase provides direct access to the underlying database implementation but you should
+ * prefer using {@link Dao} classes.
+ *
+ * @see Database
+ */
+@SuppressWarnings({"unused", "WeakerAccess"})
+public abstract class RoomDatabase {
+    private static final String DB_IMPL_SUFFIX = "_Impl";
+    // set by the generated open helper.
+    protected volatile SupportSQLiteDatabase mDatabase;
+    private SupportSQLiteOpenHelper mOpenHelper;
+    private final InvalidationTracker mInvalidationTracker;
+    private boolean mAllowMainThreadQueries;
+
+    /**
+     * Creates a RoomDatabase.
+     * <p>
+     * You cannot create an instance of a database, instead, you should acquire it via
+     * {@link Room#databaseBuilder(Context, Class, String)} or
+     * {@link Room#inMemoryDatabaseBuilder(Context, Class)}.
+     */
+    public RoomDatabase() {
+        mInvalidationTracker = createInvalidationTracker();
+    }
+
+    /**
+     * Called by {@link Room} when it is initialized.
+     *
+     * @param configuration The database configuration.
+     */
+    @CallSuper
+    public void init(DatabaseConfiguration configuration) {
+        mOpenHelper = createOpenHelper(configuration);
+        mAllowMainThreadQueries = configuration.allowMainThreadQueries;
+    }
+
+    /**
+     * Returns the SQLite open helper used by this database.
+     *
+     * @return The SQLite open helper used by this database.
+     */
+    public SupportSQLiteOpenHelper getOpenHelper() {
+        return mOpenHelper;
+    }
+
+    /**
+     * Creates the open helper to access the database. Generated class already implements this
+     * method.
+     * Note that this method is called when the RoomDatabase is initialized.
+     *
+     * @param config The configuration of the Room database.
+     * @return A new SupportSQLiteOpenHelper to be used while connecting to the database.
+     */
+    protected abstract SupportSQLiteOpenHelper createOpenHelper(DatabaseConfiguration config);
+
+    /**
+     * Called when the RoomDatabase is created.
+     * <p>
+     * This is already implemented by the generated code.
+     *
+     * @return Creates a new InvalidationTracker.
+     */
+    protected abstract InvalidationTracker createInvalidationTracker();
+
+    /**
+     * Returns true if database connection is open and initialized.
+     *
+     * @return true if the database connection is open, false otherwise.
+     */
+    public boolean isOpen() {
+        final SupportSQLiteDatabase db = mDatabase;
+        return db != null && db.isOpen();
+    }
+
+    /**
+     * Closes the database if it is already open.
+     */
+    public void close() {
+        if (isOpen()) {
+            mOpenHelper.close();
+        }
+    }
+
+    /**
+     * Asserts that we are not on the main thread.
+     *
+     * @hide
+     */
+    @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
+    public void assertNotMainThread() {
+        if (mAllowMainThreadQueries) {
+            return;
+        }
+        if (AppToolkitTaskExecutor.getInstance().isMainThread()) {
+            throw new IllegalStateException("Cannot access database on the main thread since"
+                    + " it may potentially lock the UI for a long period of time.");
+        }
+    }
+
+    // Below, there are wrapper methods for SupportSQLiteDatabase. This helps us track which
+    // methods we are using and also helps unit tests to mock this class without mocking
+    // all SQLite database methods.
+
+    /**
+     * Convenience method to query the database with arguments.
+     *
+     * @param query The sql query
+     * @param args The bind arguments for the placeholders in the query
+     *
+     * @return A Cursor obtained by running the given query in the Room database.
+     */
+    public Cursor query(String query, @Nullable Object[] args) {
+        return mOpenHelper.getWritableDatabase().query(new SimpleSQLiteQuery(query, args));
+    }
+
+    /**
+     * Wrapper for {@link SupportSQLiteDatabase#query(SupportSQLiteQuery)}.
+     *
+     * @param query The Query which includes the SQL and a bind callback for bind arguments.
+     * @return Result of the query.
+     */
+    public Cursor query(SupportSQLiteQuery query) {
+        assertNotMainThread();
+        return mOpenHelper.getWritableDatabase().query(query);
+    }
+
+    /**
+     * Wrapper for {@link SupportSQLiteDatabase#compileStatement(String)}.
+     *
+     * @param sql The query to compile.
+     * @return The compiled query.
+     */
+    public SupportSQLiteStatement compileStatement(String sql) {
+        assertNotMainThread();
+        return mOpenHelper.getWritableDatabase().compileStatement(sql);
+    }
+
+    /**
+     * Wrapper for {@link SupportSQLiteDatabase#beginTransaction()}.
+     */
+    public void beginTransaction() {
+        assertNotMainThread();
+        mInvalidationTracker.syncTriggers();
+        mOpenHelper.getWritableDatabase().beginTransaction();
+    }
+
+    /**
+     * Wrapper for {@link SupportSQLiteDatabase#endTransaction()}.
+     */
+    public void endTransaction() {
+        mOpenHelper.getWritableDatabase().endTransaction();
+        mInvalidationTracker.refreshVersionsAsync();
+    }
+
+    /**
+     * Wrapper for {@link SupportSQLiteDatabase#setTransactionSuccessful()}.
+     */
+    public void setTransactionSuccessful() {
+        mOpenHelper.getWritableDatabase().setTransactionSuccessful();
+    }
+
+    /**
+     * Executes the specified {@link Runnable} in a database transaction. The transaction will be
+     * marked as successful unless an exception is thrown in the {@link Runnable}.
+     *
+     * @param body The piece of code to execute.
+     */
+    public void runInTransaction(Runnable body) {
+        beginTransaction();
+        try {
+            body.run();
+            setTransactionSuccessful();
+        } finally {
+            endTransaction();
+        }
+    }
+
+    /**
+     * Executes the specified {@link Callable} in a database transaction. The transaction will be
+     * marked as successful unless an exception is thrown in the {@link Callable}.
+     *
+     * @param body The piece of code to execute.
+     * @param <V>  The type of the return value.
+     * @return The value returned from the {@link Callable}.
+     */
+    public <V> V runInTransaction(Callable<V> body) {
+        beginTransaction();
+        try {
+            V result = body.call();
+            setTransactionSuccessful();
+            return result;
+        } catch (RuntimeException e) {
+            throw e;
+        } catch (Exception e) {
+            throw new RuntimeException("Exception in transaction", e);
+        } finally {
+            endTransaction();
+        }
+    }
+
+    /**
+     * Called by the generated code when database is open.
+     * <p>
+     * You should never call this method manually.
+     *
+     * @param db The database instance.
+     */
+    protected void internalInitInvalidationTracker(SupportSQLiteDatabase db) {
+        mInvalidationTracker.internalInit(db);
+    }
+
+    /**
+     * Returns the invalidation tracker for this database.
+     * <p>
+     * You can use the invalidation tracker to get notified when certain tables in the database
+     * are modified.
+     *
+     * @return The invalidation tracker for the database.
+     */
+    public InvalidationTracker getInvalidationTracker() {
+        return mInvalidationTracker;
+    }
+
+    /**
+     * Returns true if current thread is in a transaction.
+     *
+     * @return True if there is an active transaction in current thread, false otherwise.
+     * @see SupportSQLiteDatabase#inTransaction()
+     */
+    public boolean inTransaction() {
+        return mOpenHelper.getWritableDatabase().inTransaction();
+    }
+
+    /**
+     * Builder for RoomDatabase.
+     *
+     * @param <T> The type of the abstract database class.
+     */
+    @SuppressWarnings("unused")
+    public static class Builder<T extends RoomDatabase> {
+        private final Class<T> mDatabaseClass;
+        private final String mName;
+        private final Context mContext;
+
+        private SupportSQLiteOpenHelper.Factory mFactory;
+        private boolean mInMemory;
+        private boolean mAllowMainThreadQueries;
+        /**
+         * Migrations, mapped by from-to pairs.
+         */
+        private MigrationContainer mMigrationContainer;
+
+        Builder(@NonNull Context context, @NonNull Class<T> klass, @Nullable String name) {
+            mContext = context;
+            mDatabaseClass = klass;
+            mName = name;
+            mMigrationContainer = new MigrationContainer();
+        }
+
+        /**
+         * Sets the database factory. If not set, it defaults to
+         * {@link FrameworkSQLiteOpenHelperFactory}.
+         *
+         * @param factory The factory to use to access the database.
+         * @return this
+         */
+        public Builder<T> openHelperFactory(SupportSQLiteOpenHelper.Factory factory) {
+            mFactory = factory;
+            return this;
+        }
+
+        /**
+         * Adds a migration to the builder.
+         * <p>
+         * Each Migration has a start and end versions and Room runs these migrations to bring the
+         * database to the latest version.
+         * <p>
+         * If a migration item is missing between current version and the latest version, Room
+         * will clear the database and recreate so even if you have no changes between 2 versions,
+         * you should still provide a Migration object to the builder.
+         * <p>
+         * A migration can handle more than 1 version (e.g. if you have a faster path to choose when
+         * going version 3 to 5 without going to version 4). If Room opens a database at version
+         * 3 and latest version is &gt;= 5, Room will use the migration object that can migrate from
+         * 3 to 5 instead of 3 to 4 and 4 to 5.
+         *
+         * @param migrations The migration object that can modify the database and to the necessary
+         *                   changes.
+         * @return this
+         */
+        public Builder<T> addMigrations(Migration... migrations) {
+            mMigrationContainer.addMigrations(migrations);
+            return this;
+        }
+
+        /**
+         * Disables the main thread query check for Room.
+         * <p>
+         * Room ensures that Database is never accessed on the main thread because it may lock the
+         * main thread and trigger an ANR. If you need to access the database from the main thread,
+         * you should always use async alternatives or manually move the call to a background
+         * thread.
+         * <p>
+         * You may want to turn this check off for testing.
+         *
+         * @return this
+         */
+        public Builder<T> allowMainThreadQueries() {
+            mAllowMainThreadQueries = true;
+            return this;
+        }
+
+        /**
+         * Creates the databases and initializes it.
+         * <p>
+         * By default, all RoomDatabases use in memory storage for TEMP tables and enables recursive
+         * triggers.
+         *
+         * @return A new database instance.
+         */
+        public T build() {
+            //noinspection ConstantConditions
+            if (mContext == null) {
+                throw new IllegalArgumentException("Cannot provide null context for the database.");
+            }
+            //noinspection ConstantConditions
+            if (mDatabaseClass == null) {
+                throw new IllegalArgumentException("Must provide an abstract class that"
+                        + " extends RoomDatabase");
+            }
+            if (mFactory == null) {
+                mFactory = new FrameworkSQLiteOpenHelperFactory();
+            }
+            DatabaseConfiguration configuration =
+                    new DatabaseConfiguration(mContext, mName, mFactory, mMigrationContainer,
+                            mAllowMainThreadQueries);
+            T db = Room.getGeneratedImplementation(mDatabaseClass, DB_IMPL_SUFFIX);
+            db.init(configuration);
+            return db;
+        }
+    }
+
+    /**
+     * A container to hold migrations. It also allows querying its contents to find migrations
+     * between two versions.
+     */
+    public static class MigrationContainer {
+        private SparseArrayCompat<SparseArrayCompat<Migration>> mMigrations =
+                new SparseArrayCompat<>();
+
+        /**
+         * Adds the given migrations to the list of available migrations. If 2 migrations have the
+         * same start-end versions, the latter migration overrides the previous one.
+         *
+         * @param migrations List of available migrations.
+         */
+        public void addMigrations(Migration... migrations) {
+            for (Migration migration : migrations) {
+                addMigration(migration);
+            }
+        }
+
+        private void addMigration(Migration migration) {
+            final int start = migration.startVersion;
+            final int end = migration.endVersion;
+            SparseArrayCompat<Migration> targetMap = mMigrations.get(start);
+            if (targetMap == null) {
+                targetMap = new SparseArrayCompat<>();
+                mMigrations.put(start, targetMap);
+            }
+            Migration existing = targetMap.get(end);
+            if (existing != null) {
+                Log.w(Room.LOG_TAG, "Overriding migration " + existing + " with " + migration);
+            }
+            targetMap.append(end, migration);
+        }
+
+        /**
+         * Finds the list of migrations that should be run to move from {@code start} version to
+         * {@code end} version.
+         *
+         * @param start The current database version
+         * @param end   The target database version
+         * @return An ordered list of {@link Migration} objects that should be run to migrate
+         * between the given versions. If a migration path cannot be found, returns {@code null}.
+         */
+        @Nullable
+        public List<Migration> findMigrationPath(int start, int end) {
+            if (start == end) {
+                return Collections.emptyList();
+            }
+            boolean migrateUp = end > start;
+            List<Migration> result = new ArrayList<>();
+            return findUpMigrationPath(result, migrateUp, start, end);
+        }
+
+        private List<Migration> findUpMigrationPath(List<Migration> result, boolean upgrade,
+                int start, int end) {
+            final int searchDirection = upgrade ? -1 : 1;
+            while (upgrade ? start < end : start > end) {
+                SparseArrayCompat<Migration> targetNodes = mMigrations.get(start);
+                if (targetNodes == null) {
+                    return null;
+                }
+                // keys are ordered so we can start searching from one end of them.
+                final int size = targetNodes.size();
+                final int firstIndex;
+                final int lastIndex;
+
+                if (upgrade) {
+                    firstIndex = size - 1;
+                    lastIndex = -1;
+                } else {
+                    firstIndex = 0;
+                    lastIndex = size;
+                }
+                boolean found = false;
+                for (int i = firstIndex; i != lastIndex; i += searchDirection) {
+                    int targetVersion = targetNodes.keyAt(i);
+                    if (targetVersion <= end && targetVersion > start) {
+                        result.add(targetNodes.valueAt(i));
+                        start = targetVersion;
+                        found = true;
+                        break;
+                    }
+                }
+                if (!found) {
+                    return null;
+                }
+            }
+            return result;
+        }
+    }
+}
diff --git a/room/runtime/src/main/java/android/arch/persistence/room/RoomOpenHelper.java b/room/runtime/src/main/java/android/arch/persistence/room/RoomOpenHelper.java
new file mode 100644
index 0000000..7ac73df
--- /dev/null
+++ b/room/runtime/src/main/java/android/arch/persistence/room/RoomOpenHelper.java
@@ -0,0 +1,145 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.arch.persistence.room;
+
+import android.arch.persistence.db.SimpleSQLiteQuery;
+import android.arch.persistence.db.SupportSQLiteDatabase;
+import android.arch.persistence.db.SupportSQLiteOpenHelper;
+import android.arch.persistence.room.migration.Migration;
+import android.database.Cursor;
+import android.support.annotation.NonNull;
+import android.support.annotation.Nullable;
+import android.support.annotation.RestrictTo;
+
+import java.util.List;
+
+/**
+ * An open helper that holds a reference to the configuration until the database is opened.
+ *
+ * @hide
+ */
+@SuppressWarnings("unused")
+@RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
+public class RoomOpenHelper extends SupportSQLiteOpenHelper.Callback {
+    @Nullable
+    private DatabaseConfiguration mConfiguration;
+    @NonNull
+    private final Delegate mDelegate;
+    @NonNull
+    private final String mIdentityHash;
+
+    public RoomOpenHelper(@NonNull DatabaseConfiguration configuration, @NonNull Delegate delegate,
+            @NonNull String identityHash) {
+        mConfiguration = configuration;
+        mDelegate = delegate;
+        mIdentityHash = identityHash;
+    }
+
+    @Override
+    public void onConfigure(SupportSQLiteDatabase db) {
+        super.onConfigure(db);
+    }
+
+    @Override
+    public void onCreate(SupportSQLiteDatabase db) {
+        updateIdentity(db);
+        mDelegate.createAllTables(db);
+    }
+
+    @Override
+    public void onUpgrade(SupportSQLiteDatabase db, int oldVersion, int newVersion) {
+        boolean migrated = false;
+        if (mConfiguration != null) {
+            List<Migration> migrations = mConfiguration.migrationContainer.findMigrationPath(
+                    oldVersion, newVersion);
+            if (migrations != null) {
+                for (Migration migration : migrations) {
+                    migration.migrate(db);
+                }
+                mDelegate.validateMigration(db);
+                updateIdentity(db);
+                migrated = true;
+            }
+        }
+        if (!migrated) {
+            mDelegate.dropAllTables(db);
+            mDelegate.createAllTables(db);
+        }
+    }
+
+    @Override
+    public void onDowngrade(SupportSQLiteDatabase db, int oldVersion, int newVersion) {
+        onUpgrade(db, oldVersion, newVersion);
+    }
+
+    @Override
+    public void onOpen(SupportSQLiteDatabase db) {
+        super.onOpen(db);
+        checkIdentity(db);
+        mDelegate.onOpen(db);
+        // there might be too many configurations etc, just clear it.
+        mConfiguration = null;
+    }
+
+    private void checkIdentity(SupportSQLiteDatabase db) {
+        createMasterTableIfNotExists(db);
+        String identityHash = "";
+        Cursor cursor = db.query(new SimpleSQLiteQuery(RoomMasterTable.READ_QUERY));
+        //noinspection TryFinallyCanBeTryWithResources
+        try {
+            if (cursor.moveToFirst()) {
+                identityHash = cursor.getString(0);
+            }
+        } finally {
+            cursor.close();
+        }
+        if (!mIdentityHash.equals(identityHash)) {
+            throw new IllegalStateException("Room cannot verify the data integrity. Looks like"
+                    + " you've changed schema but forgot to update the version number. You can"
+                    + " simply fix this by increasing the version number.");
+        }
+    }
+
+    private void updateIdentity(SupportSQLiteDatabase db) {
+        createMasterTableIfNotExists(db);
+        db.execSQL(RoomMasterTable.createInsertQuery(mIdentityHash));
+    }
+
+    private void createMasterTableIfNotExists(SupportSQLiteDatabase db) {
+        db.execSQL(RoomMasterTable.CREATE_QUERY);
+    }
+
+    /**
+     * @hide
+     */
+    @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
+    public abstract static class Delegate {
+        protected abstract void dropAllTables(SupportSQLiteDatabase database);
+
+        protected abstract void createAllTables(SupportSQLiteDatabase database);
+
+        protected abstract void onOpen(SupportSQLiteDatabase database);
+
+        /**
+         * Called after a migration run to validate database integrity.
+         *
+         * @param db The SQLite database.
+         */
+        protected abstract void validateMigration(SupportSQLiteDatabase db);
+    }
+
+}
diff --git a/room/runtime/src/main/java/android/arch/persistence/room/RoomSQLiteQuery.java b/room/runtime/src/main/java/android/arch/persistence/room/RoomSQLiteQuery.java
new file mode 100644
index 0000000..4fda73b
--- /dev/null
+++ b/room/runtime/src/main/java/android/arch/persistence/room/RoomSQLiteQuery.java
@@ -0,0 +1,226 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.arch.persistence.room;
+
+import android.arch.persistence.db.SupportSQLiteProgram;
+import android.arch.persistence.db.SupportSQLiteQuery;
+import android.support.annotation.IntDef;
+import android.support.annotation.RestrictTo;
+import android.support.annotation.VisibleForTesting;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.util.Arrays;
+import java.util.Iterator;
+import java.util.Map;
+import java.util.TreeMap;
+
+/**
+ * This class is used as an intermediate place to keep binding arguments so that we can run
+ * Cursor queries with correct types rather than passing everything as a string.
+ * <p>
+ * Because it is relatively a big object, they are pooled and must be released after each use.
+ *
+ * @hide
+ */
+@SuppressWarnings("unused")
+@RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
+public class RoomSQLiteQuery implements SupportSQLiteQuery, SupportSQLiteProgram {
+    @SuppressWarnings("WeakerAccess")
+    @VisibleForTesting
+    // Maximum number of queries we'll keep cached.
+    static final int POOL_LIMIT = 15;
+    @SuppressWarnings("WeakerAccess")
+    @VisibleForTesting
+    // Once we hit POOL_LIMIT, we'll bring the pool size back to the desired number. We always
+    // clear the bigger queries (# of arguments).
+    static final int DESIRED_POOL_SIZE = 10;
+    private volatile String mQuery;
+    @SuppressWarnings("WeakerAccess")
+    @VisibleForTesting
+    final long[] mLongBindings;
+    @SuppressWarnings("WeakerAccess")
+    @VisibleForTesting
+    final double[] mDoubleBindings;
+    @SuppressWarnings("WeakerAccess")
+    @VisibleForTesting
+    final String[] mStringBindings;
+    @SuppressWarnings("WeakerAccess")
+    @VisibleForTesting
+    final byte[][] mBlobBindings;
+
+    @Binding
+    private final int[] mBindingTypes;
+    @SuppressWarnings("WeakerAccess")
+    @VisibleForTesting
+    final int mCapacity;
+    // number of arguments in the query
+    @SuppressWarnings("WeakerAccess")
+    @VisibleForTesting
+    int mArgCount;
+
+
+    @SuppressWarnings("WeakerAccess")
+    @VisibleForTesting
+    static final TreeMap<Integer, RoomSQLiteQuery> sQueryPool = new TreeMap<>();
+
+    /**
+     * Returns a new RoomSQLiteQuery that can accept the given number of arguments and holds the
+     * given query.
+     *
+     * @param query         The query to prepare
+     * @param argumentCount The number of query arguments
+     * @return A RoomSQLiteQuery that holds the given query and has space for the given number of
+     * arguments.
+     */
+    @SuppressWarnings("WeakerAccess")
+    public static RoomSQLiteQuery acquire(String query, int argumentCount) {
+        synchronized (sQueryPool) {
+            final Map.Entry<Integer, RoomSQLiteQuery> entry =
+                    sQueryPool.ceilingEntry(argumentCount);
+            if (entry != null) {
+                sQueryPool.remove(entry.getKey());
+                final RoomSQLiteQuery sqliteQuery = entry.getValue();
+                sqliteQuery.init(query, argumentCount);
+                return sqliteQuery;
+            }
+        }
+        RoomSQLiteQuery sqLiteQuery = new RoomSQLiteQuery(argumentCount);
+        sqLiteQuery.init(query, argumentCount);
+        return sqLiteQuery;
+    }
+
+    private RoomSQLiteQuery(int capacity) {
+        mCapacity = capacity;
+        // because, 1 based indices... we don't want to offsets everything with 1 all the time.
+        int limit = capacity + 1;
+        //noinspection WrongConstant
+        mBindingTypes = new int[limit];
+        mLongBindings = new long[limit];
+        mDoubleBindings = new double[limit];
+        mStringBindings = new String[limit];
+        mBlobBindings = new byte[limit][];
+    }
+
+    @SuppressWarnings("WeakerAccess")
+    void init(String query, int argCount) {
+        mQuery = query;
+        mArgCount = argCount;
+    }
+
+    /**
+     * Releases the query back to the pool.
+     * <p>
+     * After released, the statement might be returned when {@link #acquire(String, int)} is called
+     * so you should never re-use it after releasing.
+     */
+    @SuppressWarnings("WeakerAccess")
+    public void release() {
+        synchronized (sQueryPool) {
+            sQueryPool.put(mCapacity, this);
+            prunePoolLocked();
+        }
+    }
+
+    private static void prunePoolLocked() {
+        if (sQueryPool.size() > POOL_LIMIT) {
+            int toBeRemoved = sQueryPool.size() - DESIRED_POOL_SIZE;
+            final Iterator<Integer> iterator = sQueryPool.descendingKeySet().iterator();
+            while (toBeRemoved-- > 0) {
+                iterator.next();
+                iterator.remove();
+            }
+        }
+    }
+
+    @Override
+    public String getSql() {
+        return mQuery;
+    }
+
+    @Override
+    public void bindTo(SupportSQLiteProgram program) {
+        for (int index = 1; index <= mArgCount; index++) {
+            switch (mBindingTypes[index]) {
+                case NULL:
+                    program.bindNull(index);
+                    break;
+                case LONG:
+                    program.bindLong(index, mLongBindings[index]);
+                    break;
+                case DOUBLE:
+                    program.bindDouble(index, mDoubleBindings[index]);
+                    break;
+                case STRING:
+                    program.bindString(index, mStringBindings[index]);
+                    break;
+                case BLOB:
+                    program.bindBlob(index, mBlobBindings[index]);
+                    break;
+            }
+        }
+    }
+
+    @Override
+    public void bindNull(int index) {
+        mBindingTypes[index] = NULL;
+    }
+
+    @Override
+    public void bindLong(int index, long value) {
+        mBindingTypes[index] = LONG;
+        mLongBindings[index] = value;
+    }
+
+    @Override
+    public void bindDouble(int index, double value) {
+        mBindingTypes[index] = DOUBLE;
+        mDoubleBindings[index] = value;
+    }
+
+    @Override
+    public void bindString(int index, String value) {
+        mBindingTypes[index] = STRING;
+        mStringBindings[index] = value;
+    }
+
+    @Override
+    public void bindBlob(int index, byte[] value) {
+        mBindingTypes[index] = BLOB;
+        mBlobBindings[index] = value;
+    }
+
+    @Override
+    public void clearBindings() {
+        Arrays.fill(mBindingTypes, NULL);
+        Arrays.fill(mStringBindings, null);
+        Arrays.fill(mBlobBindings, null);
+        mQuery = null;
+        // no need to clear others
+    }
+
+    private static final int NULL = 1;
+    private static final int LONG = 2;
+    private static final int DOUBLE = 3;
+    private static final int STRING = 4;
+    private static final int BLOB = 5;
+
+    @Retention(RetentionPolicy.SOURCE)
+    @IntDef({NULL, LONG, DOUBLE, STRING, BLOB})
+    @interface Binding {
+    }
+}
diff --git a/room/runtime/src/main/java/android/arch/persistence/room/SharedSQLiteStatement.java b/room/runtime/src/main/java/android/arch/persistence/room/SharedSQLiteStatement.java
new file mode 100644
index 0000000..6b1f8ea
--- /dev/null
+++ b/room/runtime/src/main/java/android/arch/persistence/room/SharedSQLiteStatement.java
@@ -0,0 +1,100 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package android.arch.persistence.room;
+
+import android.arch.persistence.db.SupportSQLiteStatement;
+import android.support.annotation.RestrictTo;
+
+import java.util.concurrent.atomic.AtomicBoolean;
+
+/**
+ * Represents a prepared SQLite state that can be re-used multiple times.
+ * <p>
+ * This class is used by generated code. After it is used, {@code release} must be called so that
+ * it can be used by other threads.
+ * <p>
+ * To avoid re-entry even within the same thread, this class allows only 1 time access to the shared
+ * statement until it is released.
+ *
+ * @hide
+ */
+@SuppressWarnings({"WeakerAccess", "unused"})
+@RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
+public abstract class SharedSQLiteStatement {
+    private final AtomicBoolean mLock = new AtomicBoolean(false);
+
+    private final RoomDatabase mDatabase;
+    private volatile SupportSQLiteStatement mStmt;
+
+    /**
+     * Creates an SQLite prepared statement that can be re-used across threads. If it is in use,
+     * it automatically creates a new one.
+     *
+     * @param database The database to create the statement in.
+     */
+    public SharedSQLiteStatement(RoomDatabase database) {
+        mDatabase = database;
+    }
+
+    /**
+     * Create the query.
+     *
+     * @return The SQL query to prepare.
+     */
+    protected abstract String createQuery();
+
+    protected void assertNotMainThread() {
+        mDatabase.assertNotMainThread();
+    }
+
+    private SupportSQLiteStatement createNewStatement() {
+        String query = createQuery();
+        return mDatabase.compileStatement(query);
+    }
+
+    private SupportSQLiteStatement getStmt(boolean canUseCached) {
+        final SupportSQLiteStatement stmt;
+        if (canUseCached) {
+            if (mStmt == null) {
+                mStmt = createNewStatement();
+            }
+            stmt = mStmt;
+        } else {
+            // it is in use, create a one off statement
+            stmt = createNewStatement();
+        }
+        return stmt;
+    }
+
+    /**
+     * Call this to get the statement. Must call {@link #release(SupportSQLiteStatement)} once done.
+     */
+    public SupportSQLiteStatement acquire() {
+        assertNotMainThread();
+        return getStmt(mLock.compareAndSet(false, true));
+    }
+
+    /**
+     * Must call this when statement will not be used anymore.
+     *
+     * @param statement The statement that was returned from acquire.
+     */
+    public void release(SupportSQLiteStatement statement) {
+        if (statement == mStmt) {
+            mLock.set(false);
+        }
+    }
+}
diff --git a/room/runtime/src/main/java/android/arch/persistence/room/migration/Migration.java b/room/runtime/src/main/java/android/arch/persistence/room/migration/Migration.java
new file mode 100644
index 0000000..0023a2e
--- /dev/null
+++ b/room/runtime/src/main/java/android/arch/persistence/room/migration/Migration.java
@@ -0,0 +1,59 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.arch.persistence.room.migration;
+
+import android.arch.persistence.db.SupportSQLiteDatabase;
+
+/**
+ * Base class for a database migration.
+ * <p>
+ * Each migration can move between 2 versions that are defined by {@link #startVersion} and
+ * {@link #endVersion}.
+ * <p>
+ * A migration can handle more than 1 version (e.g. if you have a faster path to choose when
+ * going version 3 to 5 without going to version 4). If Room opens a database at version
+ * 3 and latest version is &gt;= 5, Room will use the migration object that can migrate from
+ * 3 to 5 instead of 3 to 4 and 4 to 5.
+ * <p>
+ * If there are not enough migrations provided to move from the current version to the latest
+ * version, Room will clear the database and recreate so even if you have no changes between 2
+ * versions, you should still provide a Migration object to the builder.
+ */
+public abstract class Migration {
+    public final int startVersion;
+    public final int endVersion;
+
+    /**
+     * Creates a new migration between {@code startVersion} and {@code endVersion}.
+     *
+     * @param startVersion The start version of the database.
+     * @param endVersion The end version of the database after this migration is applied.
+     */
+    public Migration(int startVersion, int endVersion) {
+        this.startVersion = startVersion;
+        this.endVersion = endVersion;
+    }
+
+    /**
+     * Should run the necessary migrations.
+     * <p>
+     * This class cannot access any generated Dao in this method.
+     *
+     * @param database The database instance
+     */
+    public abstract void migrate(SupportSQLiteDatabase database);
+}
diff --git a/room/runtime/src/main/java/android/arch/persistence/room/package-info.java b/room/runtime/src/main/java/android/arch/persistence/room/package-info.java
new file mode 100644
index 0000000..faaa952
--- /dev/null
+++ b/room/runtime/src/main/java/android/arch/persistence/room/package-info.java
@@ -0,0 +1,130 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+/**
+ * Room is a Database Object Mapping library that makes it easy to access database on Android
+ * applications.
+ * <p>
+ * Rather than hiding the detail of SQLite, Room tries to embrace them by providing convenient APIs
+ * to query the database and also verify such queries at compile time. This allows you to access
+ * the full power of SQLite while having the type safety provided by Java SQL query builders.
+ * <p>
+ * There are 3 major components in Room.
+ * <ul>
+ *     <li>{@link android.arch.persistence.room.Database Database}: This annotation marks a
+ *     class as a database. It should be an abstract class that extends
+ *     {@link android.arch.persistence.room.RoomDatabase RoomDatabase}. At runtime, you can acquire
+ *     an instance of it via {@link android.arch.persistence.room.Room#databaseBuilder(
+ *     android.content.Context,java.lang.Class, java.lang.String) Room.databaseBuilder} or
+ *     {@link android.arch.persistence.room.Room#inMemoryDatabaseBuilder(android.content.Context,
+ *     java.lang.Class) Room.inMemoryDatabaseBuilder}.
+ *     <p>
+ *         This class defines the list of entities and data access objects in the database. It is
+ *         also the main access point for the underlying connection.
+ *     </li>
+ *     <li>{@link android.arch.persistence.room.Entity Entity}: This annotation marks a class as a
+ *     database row. For each {@link android.arch.persistence.room.Entity Entity}, a database table
+ *     is created to hold the items. The Entity class must be referenced in the
+ *     {@link android.arch.persistence.room.Database#entities() Database#entities} array. Each field
+ *     of the Entity is persisted in the database unless it is annotated with
+ *     {@link android.arch.persistence.room.Ignore Ignore}. Entities must have no-arg constructors.
+ *     </li>
+ *     <li>{@link android.arch.persistence.room.Dao Dao}: This annotation marks a class or interface
+ *     as a Data Access Object. Data access objects are the main component of Room that are
+ *     responsible for defining the methods that access the database. The class that is annotated
+ *     with {@link android.arch.persistence.room.Database Database} must have an abstract method
+ *     that has 0 arguments and returns the class that is annotated with Dao. While generating the
+ *     code at compile time, Room will generate an implementation of this class.
+ *     <pre>
+ *     Using Dao classes for database access rather than query builders or direct queries allows you
+ *     to keep a separation between different components and easily mock the database access while
+ *     testing your application.
+ *     </li>
+ * </ul>
+ * Below is a sample of a simple database.
+ * <pre>
+ * // File: User.java
+ * {@literal @}Entity
+ * public class User {
+ *   {@literal @}PrimaryKey
+ *   private int uid;
+ *   private String name;
+ *   {@literal @}ColumnInfo(name = "last_name")
+ *   private String lastName;
+ *   // getters and setters are ignored for brevity but they are required for Room to work.
+ * }
+ * // File: UserDao.java
+ * {@literal @}Dao
+ * public interface UserDao {
+ *   {@literal @}Query("SELECT * FROM user")
+ *   List&lt;User&gt; loadAll();
+ *   {@literal @}Query("SELECT * FROM user WHERE uid IN (:userIds)")
+ *   List&lt;User&gt; loadAllByUserId(int... userIds);
+ *   {@literal @}Query("SELECT * FROM user where name LIKE :first AND last_name LIKE :last LIMIT 1")
+ *   User loadOneByNameAndLastName(String first, String last);
+ *   {@literal @}Insert
+ *   void insertAll(User... users);
+ *   {@literal @}Delete
+ *   void delete(User user);
+ * }
+ * // File: AppDatabase.java
+ * {@literal @}Database(entities = {User.java})
+ * public abstract class AppDatabase extends RoomDatabase {
+ *   public abstract UserDao userDao();
+ * }
+ * </pre>
+ * You can create an instance of {@code AppDatabase} as follows:
+ * <pre>
+ * AppDatabase db = Room
+ *     .databaseBuilder(getApplicationContext(), AppDatabase.class, "database-name")
+ *     .build();
+ * </pre>
+ * Since Room verifies your queries at compile time, it also detects information about which tables
+ * are accessed by the query or what columns are present in the response.
+ * <p>
+ * You can observe a particular table for changes using the
+ * {@link android.arch.persistence.room.InvalidationTracker InvalidationTracker} class which you can
+ * acquire via {@link android.arch.persistence.room.RoomDatabase#getInvalidationTracker()
+ * RoomDatabase.getInvalidationTracker}.
+ * <p>
+ * For convenience, Room allows you to return {@link android.arch.lifecycle.LiveData
+ * LiveData} from {@link android.arch.persistence.room.Query Query} methods. It will automatically
+ * observe the related tables as long as the {@code LiveData} has active observers.
+ * <pre>
+ * // This live data will automatically dispatch changes as the database changes.
+ * {@literal @}Query("SELECT * FROM user ORDER BY name LIMIT 5")
+ * LiveData&lt;User&gt; loadFirstFiveUsers();
+ * </pre>
+ * <p>
+ * You can also return arbitrary Java objects from your query results as long as the fields in the
+ * object match the list of columns in the query response. This makes it very easy to write
+ * applications that drive the UI from persistent storage.
+ * <pre>
+ * class IdAndFullName {
+ *     public int uid;
+ *     {@literal @}ColumnInfo(name = "full_name")
+ *     public String fullName;
+ * }
+ * // DAO
+ * {@literal @}Query("SELECT uid, name || lastName as full_name FROM user")
+ * public IdAndFullName[] loadFullNames();
+ * </pre>
+ * If there is a mismatch between the query result and the POJO, Room will print a warning during
+ * compilation.
+ * <p>
+ * Please see the documentation of individual classes for details.
+ */
+package android.arch.persistence.room;
diff --git a/room/runtime/src/main/java/android/arch/persistence/room/util/StringUtil.java b/room/runtime/src/main/java/android/arch/persistence/room/util/StringUtil.java
new file mode 100644
index 0000000..bee05dd
--- /dev/null
+++ b/room/runtime/src/main/java/android/arch/persistence/room/util/StringUtil.java
@@ -0,0 +1,109 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.arch.persistence.room.util;
+
+import android.support.annotation.Nullable;
+import android.util.Log;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.StringTokenizer;
+
+/**
+ * String utilities for Room
+ */
+@SuppressWarnings("WeakerAccess")
+public class StringUtil {
+    public static final String[] EMPTY_STRING_ARRAY = new String[0];
+    /**
+     * Returns a new StringBuilder to be used while producing SQL queries.
+     *
+     * @return A new or recycled StringBuilder
+     */
+    public static StringBuilder newStringBuilder() {
+        // TODO pool:
+        return new StringBuilder();
+    }
+
+    /**
+     * Adds bind variable placeholders (?) to the given string. Each placeholder is separated
+     * by a comma.
+     *
+     * @param builder The StringBuilder for the query
+     * @param count Number of placeholders
+     */
+    public static void appendPlaceholders(StringBuilder builder, int count) {
+        for (int i = 0; i < count; i++) {
+            builder.append("?");
+            if (i < count - 1) {
+                builder.append(",");
+            }
+        }
+    }
+    /**
+     * Splits a comma separated list of integers to integer list.
+     * <p>
+     * If an input is malformed, it is omitted from the result.
+     *
+     * @param input Comma separated list of integers.
+     * @return A List containing the integers or null if the input is null.
+     */
+    @Nullable
+    public static List<Integer> splitToIntList(@Nullable String input) {
+        if (input == null) {
+            return null;
+        }
+        List<Integer> result = new ArrayList<>();
+        StringTokenizer tokenizer = new StringTokenizer(input, ",");
+        while (tokenizer.hasMoreElements()) {
+            final String item = tokenizer.nextToken();
+            try {
+                result.add(Integer.parseInt(item));
+            } catch (NumberFormatException ex) {
+                Log.e("ROOM", "Malformed integer list", ex);
+            }
+        }
+        return result;
+    }
+
+    /**
+     * Joins the given list of integers into a comma separated list.
+     *
+     * @param input The list of integers.
+     * @return Comma separated string composed of integers in the list. If the list is null, return
+     * value is null.
+     */
+    @Nullable
+    public static String joinIntoString(@Nullable List<Integer> input) {
+        if (input == null) {
+            return null;
+        }
+
+        final int size = input.size();
+        if (size == 0) {
+            return "";
+        }
+        StringBuilder sb = new StringBuilder();
+        for (int i = 0; i < size; i++) {
+            sb.append(Integer.toString(input.get(i)));
+            if (i < size - 1) {
+                sb.append(",");
+            }
+        }
+        return sb.toString();
+    }
+}
diff --git a/room/runtime/src/main/java/android/arch/persistence/room/util/TableInfo.java b/room/runtime/src/main/java/android/arch/persistence/room/util/TableInfo.java
new file mode 100644
index 0000000..c9d2021
--- /dev/null
+++ b/room/runtime/src/main/java/android/arch/persistence/room/util/TableInfo.java
@@ -0,0 +1,372 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.arch.persistence.room.util;
+
+import android.arch.persistence.db.SupportSQLiteDatabase;
+import android.database.Cursor;
+import android.os.Build;
+import android.support.annotation.NonNull;
+import android.support.annotation.RestrictTo;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
+/**
+ * A data class that holds the information about a table.
+ * <p>
+ * It directly maps to the result of {@code PRAGMA table_info(<table_name>)}. Check the
+ * <a href="http://www.sqlite.org/pragma.html#pragma_table_info">PRAGMA table_info</a>
+ * documentation for more details.
+ * <p>
+ * Even though SQLite column names are case insensitive, this class uses case sensitive matching.
+ *
+ * @hide
+ */
+@RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
+@SuppressWarnings({"WeakerAccess", "unused", "TryFinallyCanBeTryWithResources"})
+// if you change this class, you must change TableInfoWriter.kt
+public class TableInfo {
+    /**
+     * The table name.
+     */
+    public final String name;
+    /**
+     * Unmodifiable map of columns keyed by column name.
+     */
+    public final Map<String, Column> columns;
+
+    public final Set<ForeignKey> foreignKeys;
+
+    @SuppressWarnings("unused")
+    public TableInfo(String name, Map<String, Column> columns, Set<ForeignKey> foreignKeys) {
+        this.name = name;
+        this.columns = Collections.unmodifiableMap(columns);
+        this.foreignKeys = Collections.unmodifiableSet(foreignKeys);
+    }
+
+    /**
+     * Reads the table information from the given database.
+     *
+     * @param database  The database to read the information from.
+     * @param tableName The table name.
+     * @return A TableInfo containing the schema information for the provided table name.
+     */
+    @SuppressWarnings("SameParameterValue")
+    public static TableInfo read(SupportSQLiteDatabase database, String tableName) {
+        Map<String, Column> columns = readColumns(database, tableName);
+        Set<ForeignKey> foreignKeys = readForeignKeys(database, tableName);
+        return new TableInfo(tableName, columns, foreignKeys);
+    }
+
+    private static Set<ForeignKey> readForeignKeys(SupportSQLiteDatabase database,
+            String tableName) {
+        Set<ForeignKey> foreignKeys = new HashSet<>();
+        // this seems to return everything in order but it is not documented so better be safe
+        Cursor cursor = database.query("PRAGMA foreign_key_list(`" + tableName + "`)");
+        try {
+            final int idColumnIndex = cursor.getColumnIndex("id");
+            final int seqColumnIndex = cursor.getColumnIndex("seq");
+            final int tableColumnIndex = cursor.getColumnIndex("table");
+            final int onDeleteColumnIndex = cursor.getColumnIndex("on_delete");
+            final int onUpdateColumnIndex = cursor.getColumnIndex("on_update");
+
+            final List<ForeignKeyWithSequence> ordered = readForeignKeyFieldMappings(cursor);
+            final int count = cursor.getCount();
+            for (int position = 0; position < count; position++) {
+                cursor.moveToPosition(position);
+                final int seq = cursor.getInt(seqColumnIndex);
+                if (seq != 0) {
+                    continue;
+                }
+                final int id = cursor.getInt(idColumnIndex);
+                List<String> myColumns = new ArrayList<>();
+                List<String> refColumns = new ArrayList<>();
+                for (ForeignKeyWithSequence key : ordered) {
+                    if (key.mId == id) {
+                        myColumns.add(key.mFrom);
+                        refColumns.add(key.mTo);
+                    }
+                }
+                foreignKeys.add(new ForeignKey(
+                        cursor.getString(tableColumnIndex),
+                        cursor.getString(onDeleteColumnIndex),
+                        cursor.getString(onUpdateColumnIndex),
+                        myColumns,
+                        refColumns
+                ));
+            }
+        } finally {
+            cursor.close();
+        }
+        return foreignKeys;
+    }
+
+    private static List<ForeignKeyWithSequence> readForeignKeyFieldMappings(Cursor cursor) {
+        final int idColumnIndex = cursor.getColumnIndex("id");
+        final int seqColumnIndex = cursor.getColumnIndex("seq");
+        final int fromColumnIndex = cursor.getColumnIndex("from");
+        final int toColumnIndex = cursor.getColumnIndex("to");
+        final int count = cursor.getCount();
+        List<ForeignKeyWithSequence> result = new ArrayList<>();
+        for (int i = 0; i < count; i++) {
+            cursor.moveToPosition(i);
+            result.add(new ForeignKeyWithSequence(
+                    cursor.getInt(idColumnIndex),
+                    cursor.getInt(seqColumnIndex),
+                    cursor.getString(fromColumnIndex),
+                    cursor.getString(toColumnIndex)
+            ));
+        }
+        Collections.sort(result);
+        return result;
+    }
+
+    private static Map<String, Column> readColumns(SupportSQLiteDatabase database,
+            String tableName) {
+        Cursor cursor = database
+                .query("PRAGMA table_info(`" + tableName + "`)");
+        //noinspection TryFinallyCanBeTryWithResources
+        Map<String, Column> columns = new HashMap<>();
+        try {
+            if (cursor.getColumnCount() > 0) {
+                int nameIndex = cursor.getColumnIndex("name");
+                int typeIndex = cursor.getColumnIndex("type");
+                int pkIndex = cursor.getColumnIndex("pk");
+
+                while (cursor.moveToNext()) {
+                    final String name = cursor.getString(nameIndex);
+                    final String type = cursor.getString(typeIndex);
+                    final int primaryKeyPosition = cursor.getInt(pkIndex);
+                    columns.put(name, new Column(name, type, primaryKeyPosition));
+                }
+            }
+        } finally {
+            cursor.close();
+        }
+        return columns;
+    }
+
+    @Override
+    public boolean equals(Object o) {
+        if (this == o) return true;
+        if (o == null || getClass() != o.getClass()) return false;
+
+        TableInfo tableInfo = (TableInfo) o;
+
+        if (!name.equals(tableInfo.name)) return false;
+        //noinspection SimplifiableIfStatement
+        if (!columns.equals(tableInfo.columns)) return false;
+        return foreignKeys.equals(tableInfo.foreignKeys);
+    }
+
+    @Override
+    public int hashCode() {
+        int result = name.hashCode();
+        result = 31 * result + columns.hashCode();
+        result = 31 * result + foreignKeys.hashCode();
+        return result;
+    }
+
+    @Override
+    public String toString() {
+        return "TableInfo{"
+                + "name='" + name + '\''
+                + ", columns=" + columns
+                + ", foreignKeys=" + foreignKeys
+                + '}';
+    }
+
+    /**
+     * Holds the information about a database column.
+     */
+    @SuppressWarnings("WeakerAccess")
+    public static class Column {
+        /**
+         * The column name.
+         */
+        public final String name;
+        /**
+         * The column type affinity.
+         */
+        public final String type;
+        /**
+         * The position of the column in the list of primary keys, 0 if the column is not part
+         * of the primary key.
+         * <p>
+         * This information is only available in API 20+.
+         * <a href="https://www.sqlite.org/releaselog/3_7_16_2.html">(SQLite version 3.7.16.2)</a>
+         * On older platforms, it will be 1 if the column is part of the primary key and 0
+         * otherwise.
+         * <p>
+         * The {@link #equals(Object)} implementation handles this inconsistency based on
+         * API levels os if you are using a custom SQLite deployment, it may return false
+         * positives.
+         */
+        public final int primaryKeyPosition;
+
+        // if you change this constructor, you must change TableInfoWriter.kt
+        public Column(String name, String type, int primaryKeyPosition) {
+            this.name = name;
+            this.type = type;
+            this.primaryKeyPosition = primaryKeyPosition;
+        }
+
+        @Override
+        public boolean equals(Object o) {
+            if (this == o) return true;
+            if (o == null || getClass() != o.getClass()) return false;
+
+            Column column = (Column) o;
+            if (Build.VERSION.SDK_INT >= 20) {
+                if (primaryKeyPosition != column.primaryKeyPosition) return false;
+            } else {
+                if (isPrimaryKey() != column.isPrimaryKey()) return false;
+            }
+
+            //noinspection SimplifiableIfStatement
+            if (!name.equals(column.name)) return false;
+            return type != null ? type.equalsIgnoreCase(column.type) : column.type == null;
+        }
+
+        /**
+         * Returns whether this column is part of the primary key or not.
+         *
+         * @return True if this column is part of the primary key, false otherwise.
+         */
+        public boolean isPrimaryKey() {
+            return primaryKeyPosition > 0;
+        }
+
+        @Override
+        public int hashCode() {
+            int result = name.hashCode();
+            result = 31 * result + (type != null ? type.hashCode() : 0);
+            result = 31 * result + primaryKeyPosition;
+            return result;
+        }
+
+        @Override
+        public String toString() {
+            return "Column{"
+                    + "name='" + name + '\''
+                    + ", type='" + type + '\''
+                    + ", primaryKeyPosition=" + primaryKeyPosition
+                    + '}';
+        }
+    }
+
+    /**
+     * Holds the information about an SQLite foreign key
+     *
+     * @hide
+     */
+    @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
+    public static class ForeignKey {
+        @NonNull
+        public final String referenceTable;
+        @NonNull
+        public final String onDelete;
+        @NonNull
+        public final String onUpdate;
+        @NonNull
+        public final List<String> columnNames;
+        @NonNull
+        public final List<String> referenceColumnNames;
+
+        public ForeignKey(@NonNull String referenceTable, @NonNull String onDelete,
+                @NonNull String onUpdate,
+                @NonNull List<String> columnNames, @NonNull List<String> referenceColumnNames) {
+            this.referenceTable = referenceTable;
+            this.onDelete = onDelete;
+            this.onUpdate = onUpdate;
+            this.columnNames = Collections.unmodifiableList(columnNames);
+            this.referenceColumnNames = Collections.unmodifiableList(referenceColumnNames);
+        }
+
+        @Override
+        public boolean equals(Object o) {
+            if (this == o) return true;
+            if (o == null || getClass() != o.getClass()) return false;
+
+            ForeignKey that = (ForeignKey) o;
+
+            if (!referenceTable.equals(that.referenceTable)) return false;
+            if (!onDelete.equals(that.onDelete)) return false;
+            if (!onUpdate.equals(that.onUpdate)) return false;
+            //noinspection SimplifiableIfStatement
+            if (!columnNames.equals(that.columnNames)) return false;
+            return referenceColumnNames.equals(that.referenceColumnNames);
+        }
+
+        @Override
+        public int hashCode() {
+            int result = referenceTable.hashCode();
+            result = 31 * result + onDelete.hashCode();
+            result = 31 * result + onUpdate.hashCode();
+            result = 31 * result + columnNames.hashCode();
+            result = 31 * result + referenceColumnNames.hashCode();
+            return result;
+        }
+
+        @Override
+        public String toString() {
+            return "ForeignKey{"
+                    + "referenceTable='" + referenceTable + '\''
+                    + ", onDelete='" + onDelete + '\''
+                    + ", onUpdate='" + onUpdate + '\''
+                    + ", columnNames=" + columnNames
+                    + ", referenceColumnNames=" + referenceColumnNames
+                    + '}';
+        }
+    }
+
+    /**
+     * Temporary data holder for a foreign key row in the pragma result. We need this to ensure
+     * sorting in the generated foreign key object.
+     *
+     * @hide
+     */
+    @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
+    static class ForeignKeyWithSequence implements Comparable<ForeignKeyWithSequence> {
+        final int mId;
+        final int mSequence;
+        final String mFrom;
+        final String mTo;
+
+        ForeignKeyWithSequence(int id, int sequence, String from, String to) {
+            mId = id;
+            mSequence = sequence;
+            mFrom = from;
+            mTo = to;
+        }
+
+        @Override
+        public int compareTo(ForeignKeyWithSequence o) {
+            final int idCmp = mId - o.mId;
+            if (idCmp == 0) {
+                return mSequence - o.mSequence;
+            } else {
+                return idCmp;
+            }
+        }
+    }
+}
diff --git a/room/runtime/src/test/java/android/arch/persistence/room/BuilderTest.java b/room/runtime/src/test/java/android/arch/persistence/room/BuilderTest.java
new file mode 100644
index 0000000..ecf6cc4
--- /dev/null
+++ b/room/runtime/src/test/java/android/arch/persistence/room/BuilderTest.java
@@ -0,0 +1,161 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.arch.persistence.room;
+
+import static org.hamcrest.CoreMatchers.instanceOf;
+import static org.hamcrest.CoreMatchers.is;
+import static org.hamcrest.CoreMatchers.notNullValue;
+import static org.hamcrest.CoreMatchers.nullValue;
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.mockito.Mockito.mock;
+
+import static java.util.Arrays.asList;
+
+import android.arch.persistence.db.SupportSQLiteDatabase;
+import android.arch.persistence.db.SupportSQLiteOpenHelper;
+import android.arch.persistence.db.framework.FrameworkSQLiteOpenHelperFactory;
+import android.arch.persistence.room.migration.Migration;
+import android.content.Context;
+
+import org.hamcrest.CoreMatchers;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+
+import java.util.List;
+
+@SuppressWarnings("ArraysAsListWithZeroOrOneArgument")
+@RunWith(JUnit4.class)
+public class BuilderTest {
+    @Test(expected = IllegalArgumentException.class)
+    public void nullContext() {
+        //noinspection ConstantConditions
+        Room.databaseBuilder(null, RoomDatabase.class, "bla").build();
+    }
+
+    @Test(expected = IllegalArgumentException.class)
+    public void nullContext2() {
+        //noinspection ConstantConditions
+        Room.inMemoryDatabaseBuilder(null, RoomDatabase.class).build();
+    }
+
+    @Test(expected = IllegalArgumentException.class)
+    public void nullName() {
+        //noinspection ConstantConditions
+        Room.databaseBuilder(mock(Context.class), RoomDatabase.class, null).build();
+    }
+
+    @Test(expected = IllegalArgumentException.class)
+    public void emptyName() {
+        Room.databaseBuilder(mock(Context.class), RoomDatabase.class, "  ").build();
+    }
+
+    @Test
+    public void migration() {
+        Migration m1 = new EmptyMigration(0, 1);
+        Migration m2 = new EmptyMigration(1, 2);
+        TestDatabase db = Room.databaseBuilder(mock(Context.class), TestDatabase.class, "foo")
+                .addMigrations(m1, m2).build();
+        DatabaseConfiguration config = ((BuilderTest_TestDatabase_Impl) db).mConfig;
+        RoomDatabase.MigrationContainer migrations = config.migrationContainer;
+        assertThat(migrations.findMigrationPath(0, 1), is(asList(m1)));
+        assertThat(migrations.findMigrationPath(1, 2), is(asList(m2)));
+        assertThat(migrations.findMigrationPath(0, 2), is(asList(m1, m2)));
+        assertThat(migrations.findMigrationPath(2, 0), CoreMatchers.<List<Migration>>nullValue());
+        assertThat(migrations.findMigrationPath(0, 3), CoreMatchers.<List<Migration>>nullValue());
+    }
+
+    @Test
+    public void migrationOverride() {
+        Migration m1 = new EmptyMigration(0, 1);
+        Migration m2 = new EmptyMigration(1, 2);
+        Migration m3 = new EmptyMigration(0, 1);
+        TestDatabase db = Room.databaseBuilder(mock(Context.class), TestDatabase.class, "foo")
+                .addMigrations(m1, m2, m3).build();
+        DatabaseConfiguration config = ((BuilderTest_TestDatabase_Impl) db).mConfig;
+        RoomDatabase.MigrationContainer migrations = config.migrationContainer;
+        assertThat(migrations.findMigrationPath(0, 1), is(asList(m3)));
+        assertThat(migrations.findMigrationPath(1, 2), is(asList(m2)));
+        assertThat(migrations.findMigrationPath(0, 3), CoreMatchers.<List<Migration>>nullValue());
+    }
+
+    @Test
+    public void migrationJump() {
+        Migration m1 = new EmptyMigration(0, 1);
+        Migration m2 = new EmptyMigration(1, 2);
+        Migration m3 = new EmptyMigration(2, 3);
+        Migration m4 = new EmptyMigration(0, 3);
+        TestDatabase db = Room.databaseBuilder(mock(Context.class), TestDatabase.class, "foo")
+                .addMigrations(m1, m2, m3, m4).build();
+        DatabaseConfiguration config = ((BuilderTest_TestDatabase_Impl) db).mConfig;
+        RoomDatabase.MigrationContainer migrations = config.migrationContainer;
+        assertThat(migrations.findMigrationPath(0, 3), is(asList(m4)));
+        assertThat(migrations.findMigrationPath(1, 3), is(asList(m2, m3)));
+    }
+
+    @Test
+    public void createBasic() {
+        Context context = mock(Context.class);
+        TestDatabase db = Room.inMemoryDatabaseBuilder(context, TestDatabase.class).build();
+        assertThat(db, instanceOf(BuilderTest_TestDatabase_Impl.class));
+        DatabaseConfiguration config = ((BuilderTest_TestDatabase_Impl) db).mConfig;
+        assertThat(config, notNullValue());
+        assertThat(config.context, is(context));
+        assertThat(config.name, is(nullValue()));
+        assertThat(config.allowMainThreadQueries, is(false));
+        assertThat(config.sqliteOpenHelperFactory,
+                instanceOf(FrameworkSQLiteOpenHelperFactory.class));
+    }
+
+    @Test
+    public void createAllowMainThread() {
+        Context context = mock(Context.class);
+        TestDatabase db = Room.inMemoryDatabaseBuilder(context, TestDatabase.class)
+                .allowMainThreadQueries()
+                .build();
+        DatabaseConfiguration config = ((BuilderTest_TestDatabase_Impl) db).mConfig;
+        assertThat(config.allowMainThreadQueries, is(true));
+    }
+
+    @Test
+    public void createWithFactoryAndVersion() {
+        Context context = mock(Context.class);
+        SupportSQLiteOpenHelper.Factory factory = mock(SupportSQLiteOpenHelper.Factory.class);
+
+        TestDatabase db = Room.inMemoryDatabaseBuilder(context, TestDatabase.class)
+                .openHelperFactory(factory)
+                .build();
+        assertThat(db, instanceOf(BuilderTest_TestDatabase_Impl.class));
+        DatabaseConfiguration config = ((BuilderTest_TestDatabase_Impl) db).mConfig;
+        assertThat(config, notNullValue());
+        assertThat(config.sqliteOpenHelperFactory, is(factory));
+    }
+
+    abstract static class TestDatabase extends RoomDatabase {
+    }
+
+    static class EmptyMigration extends Migration {
+        EmptyMigration(int start, int end) {
+            super(start, end);
+        }
+
+        @Override
+        public void migrate(SupportSQLiteDatabase database) {
+        }
+    }
+
+}
diff --git a/room/runtime/src/test/java/android/arch/persistence/room/BuilderTest_TestDatabase_Impl.java b/room/runtime/src/test/java/android/arch/persistence/room/BuilderTest_TestDatabase_Impl.java
new file mode 100644
index 0000000..d261454
--- /dev/null
+++ b/room/runtime/src/test/java/android/arch/persistence/room/BuilderTest_TestDatabase_Impl.java
@@ -0,0 +1,38 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.arch.persistence.room;
+
+import android.arch.persistence.db.SupportSQLiteOpenHelper;
+
+public class BuilderTest_TestDatabase_Impl extends BuilderTest.TestDatabase {
+    DatabaseConfiguration mConfig;
+    @Override
+    public void init(DatabaseConfiguration configuration) {
+        super.init(configuration);
+        mConfig = configuration;
+    }
+
+    @Override
+    protected SupportSQLiteOpenHelper createOpenHelper(DatabaseConfiguration config) {
+        return null;
+    }
+
+    @Override
+    protected InvalidationTracker createInvalidationTracker() {
+        return null;
+    }
+}
diff --git a/room/runtime/src/test/java/android/arch/persistence/room/InvalidationTrackerTest.java b/room/runtime/src/test/java/android/arch/persistence/room/InvalidationTrackerTest.java
new file mode 100644
index 0000000..cd2375e
--- /dev/null
+++ b/room/runtime/src/test/java/android/arch/persistence/room/InvalidationTrackerTest.java
@@ -0,0 +1,339 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.arch.persistence.room;
+
+import static org.hamcrest.CoreMatchers.is;
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.hamcrest.core.IsCollectionContaining.hasItem;
+import static org.hamcrest.core.IsCollectionContaining.hasItems;
+import static org.mockito.Matchers.any;
+import static org.mockito.Matchers.anyInt;
+import static org.mockito.Matchers.anyString;
+import static org.mockito.Matchers.eq;
+import static org.mockito.Mockito.doReturn;
+import static org.mockito.Mockito.doThrow;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.reset;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import android.arch.core.executor.JunitTaskExecutorRule;
+import android.arch.persistence.db.SupportSQLiteDatabase;
+import android.arch.persistence.db.SupportSQLiteOpenHelper;
+import android.arch.persistence.db.SupportSQLiteStatement;
+import android.database.Cursor;
+import android.database.sqlite.SQLiteException;
+import android.support.annotation.NonNull;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+import org.mockito.Mockito;
+import org.mockito.invocation.InvocationOnMock;
+import org.mockito.stubbing.Answer;
+
+import java.lang.ref.WeakReference;
+import java.util.ArrayList;
+import java.util.Locale;
+import java.util.Set;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.atomic.AtomicInteger;
+
+@RunWith(JUnit4.class)
+public class InvalidationTrackerTest {
+    private InvalidationTracker mTracker;
+    private RoomDatabase mRoomDatabase;
+    private SupportSQLiteOpenHelper mOpenHelper;
+    @Rule
+    public JunitTaskExecutorRule mTaskExecutorRule = new JunitTaskExecutorRule(1, true);
+
+    @Before
+    public void setup() {
+        mRoomDatabase = mock(RoomDatabase.class);
+        SupportSQLiteDatabase sqliteDb = mock(SupportSQLiteDatabase.class);
+        final SupportSQLiteStatement statement = mock(SupportSQLiteStatement.class);
+        mOpenHelper = mock(SupportSQLiteOpenHelper.class);
+
+        doReturn(statement).when(sqliteDb).compileStatement(eq(InvalidationTracker.CLEANUP_SQL));
+        doReturn(sqliteDb).when(mOpenHelper).getWritableDatabase();
+        doReturn(true).when(mRoomDatabase).isOpen();
+        //noinspection ResultOfMethodCallIgnored
+        doReturn(mOpenHelper).when(mRoomDatabase).getOpenHelper();
+
+        mTracker = new InvalidationTracker(mRoomDatabase, "a", "B", "i");
+        mTracker.internalInit(sqliteDb);
+    }
+
+    @Before
+    public void setLocale() {
+        Locale.setDefault(Locale.forLanguageTag("tr-TR"));
+    }
+
+    @After
+    public void unsetLocale() {
+        Locale.setDefault(Locale.US);
+    }
+
+    @Test
+    public void tableIds() {
+        assertThat(mTracker.mTableIdLookup.get("a"), is(0));
+        assertThat(mTracker.mTableIdLookup.get("b"), is(1));
+    }
+
+    @Test
+    public void testWeak() throws InterruptedException {
+        final AtomicInteger data = new AtomicInteger(0);
+        InvalidationTracker.Observer observer = new InvalidationTracker.Observer("a") {
+            @Override
+            public void onInvalidated(@NonNull Set<String> tables) {
+                data.incrementAndGet();
+            }
+        };
+        mTracker.addWeakObserver(observer);
+        setVersions(1, 0);
+        refreshSync();
+        assertThat(data.get(), is(1));
+        observer = null;
+        forceGc();
+        setVersions(2, 0);
+        refreshSync();
+        assertThat(data.get(), is(1));
+    }
+
+    @Test
+    public void addRemoveObserver() throws Exception {
+        InvalidationTracker.Observer observer = new LatchObserver(1, "a");
+        mTracker.addObserver(observer);
+        drainTasks();
+        assertThat(mTracker.mObserverMap.size(), is(1));
+        mTracker.removeObserver(new LatchObserver(1, "a"));
+        drainTasks();
+        assertThat(mTracker.mObserverMap.size(), is(1));
+        mTracker.removeObserver(observer);
+        drainTasks();
+        assertThat(mTracker.mObserverMap.size(), is(0));
+    }
+
+    private void drainTasks() throws InterruptedException {
+        mTaskExecutorRule.drainTasks(200);
+    }
+
+    @Test(expected = IllegalArgumentException.class)
+    public void badObserver() {
+        InvalidationTracker.Observer observer = new LatchObserver(1, "x");
+        mTracker.addObserver(observer);
+    }
+
+    @Test
+    public void refreshReadValues() throws Exception {
+        setVersions(1, 0, 2, 1);
+        refreshSync();
+        assertThat(mTracker.mTableVersions, is(new long[]{1, 2, 0}));
+
+        setVersions(3, 1);
+        refreshSync();
+        assertThat(mTracker.mTableVersions, is(new long[]{1, 3, 0}));
+
+        setVersions(7, 0);
+        refreshSync();
+        assertThat(mTracker.mTableVersions, is(new long[]{7, 3, 0}));
+
+        refreshSync();
+        assertThat(mTracker.mTableVersions, is(new long[]{7, 3, 0}));
+    }
+
+    private void refreshSync() throws InterruptedException {
+        mTracker.refreshVersionsAsync();
+        drainTasks();
+    }
+
+    @Test
+    public void refreshCheckTasks() throws Exception {
+        when(mRoomDatabase.query(anyString(), any(Object[].class)))
+                .thenReturn(mock(Cursor.class));
+        mTracker.refreshVersionsAsync();
+        mTracker.refreshVersionsAsync();
+        verify(mTaskExecutorRule.getTaskExecutor()).executeOnDiskIO(mTracker.mRefreshRunnable);
+        drainTasks();
+
+        reset(mTaskExecutorRule.getTaskExecutor());
+        mTracker.refreshVersionsAsync();
+        verify(mTaskExecutorRule.getTaskExecutor()).executeOnDiskIO(mTracker.mRefreshRunnable);
+    }
+
+    @Test
+    public void observe1Table() throws Exception {
+        LatchObserver observer = new LatchObserver(1, "a");
+        mTracker.addObserver(observer);
+        setVersions(1, 0, 2, 1);
+        refreshSync();
+        assertThat(observer.await(), is(true));
+        assertThat(observer.getInvalidatedTables().size(), is(1));
+        assertThat(observer.getInvalidatedTables(), hasItem("a"));
+
+        setVersions(3, 1);
+        observer.reset(1);
+        refreshSync();
+        assertThat(observer.await(), is(false));
+
+        setVersions(4, 0);
+        refreshSync();
+        assertThat(observer.await(), is(true));
+        assertThat(observer.getInvalidatedTables().size(), is(1));
+        assertThat(observer.getInvalidatedTables(), hasItem("a"));
+    }
+
+    @Test
+    public void observe2Tables() throws Exception {
+        LatchObserver observer = new LatchObserver(1, "A", "B");
+        mTracker.addObserver(observer);
+        setVersions(1, 0, 2, 1);
+        refreshSync();
+        assertThat(observer.await(), is(true));
+        assertThat(observer.getInvalidatedTables().size(), is(2));
+        assertThat(observer.getInvalidatedTables(), hasItems("A", "B"));
+
+        setVersions(3, 1);
+        observer.reset(1);
+        refreshSync();
+        assertThat(observer.await(), is(true));
+        assertThat(observer.getInvalidatedTables().size(), is(1));
+        assertThat(observer.getInvalidatedTables(), hasItem("B"));
+
+        setVersions(4, 0);
+        observer.reset(1);
+        refreshSync();
+        assertThat(observer.await(), is(true));
+        assertThat(observer.getInvalidatedTables().size(), is(1));
+        assertThat(observer.getInvalidatedTables(), hasItem("A"));
+
+        observer.reset(1);
+        refreshSync();
+        assertThat(observer.await(), is(false));
+    }
+
+    @Test
+    public void locale() {
+        LatchObserver observer = new LatchObserver(1, "I");
+        mTracker.addObserver(observer);
+    }
+
+    @Test
+    public void closedDb() {
+        doThrow(new IllegalStateException("foo")).when(mOpenHelper).getWritableDatabase();
+        mTracker.addObserver(new LatchObserver(1, "a", "b"));
+        mTracker.syncTriggers();
+        mTracker.mRefreshRunnable.run();
+    }
+
+    @Test
+    public void closedDbAfterOpen() throws InterruptedException {
+        setVersions(3, 1);
+        mTracker.addObserver(new LatchObserver(1, "a", "b"));
+        mTracker.syncTriggers();
+        mTracker.mRefreshRunnable.run();
+        doThrow(new SQLiteException("foo")).when(mRoomDatabase).query(
+                Mockito.eq(InvalidationTracker.SELECT_UPDATED_TABLES_SQL),
+                any(Object[].class));
+        mTracker.mPendingRefresh.set(true);
+        mTracker.mRefreshRunnable.run();
+    }
+
+    /**
+     * Key value pairs of VERSION, TABLE_ID
+     */
+    private void setVersions(int... keyValuePairs) throws InterruptedException {
+        // mockito does not like multi-threaded access so before setting versions, make sure we
+        // sync background tasks.
+        drainTasks();
+        Cursor cursor = createCursorWithValues(keyValuePairs);
+        doReturn(cursor).when(mRoomDatabase).query(
+                Mockito.eq(InvalidationTracker.SELECT_UPDATED_TABLES_SQL),
+                any(Object[].class)
+        );
+    }
+
+    private Cursor createCursorWithValues(final int... keyValuePairs) {
+        Cursor cursor = mock(Cursor.class);
+        final AtomicInteger index = new AtomicInteger(-2);
+        when(cursor.moveToNext()).thenAnswer(new Answer<Boolean>() {
+            @Override
+            public Boolean answer(InvocationOnMock invocation) throws Throwable {
+                return index.addAndGet(2) < keyValuePairs.length;
+            }
+        });
+        Answer<Integer> intAnswer = new Answer<Integer>() {
+            @Override
+            public Integer answer(InvocationOnMock invocation) throws Throwable {
+                return keyValuePairs[index.intValue() + (Integer) invocation.getArguments()[0]];
+            }
+        };
+        Answer<Long> longAnswer = new Answer<Long>() {
+            @Override
+            public Long answer(InvocationOnMock invocation) throws Throwable {
+                return (long) keyValuePairs[index.intValue()
+                        + (Integer) invocation.getArguments()[0]];
+            }
+        };
+        when(cursor.getInt(anyInt())).thenAnswer(intAnswer);
+        when(cursor.getLong(anyInt())).thenAnswer(longAnswer);
+        return cursor;
+    }
+
+    static class LatchObserver extends InvalidationTracker.Observer {
+        private CountDownLatch mLatch;
+        private Set<String> mInvalidatedTables;
+
+        LatchObserver(int count, String... tableNames) {
+            super(tableNames);
+            mLatch = new CountDownLatch(count);
+        }
+
+        boolean await() throws InterruptedException {
+            return mLatch.await(3, TimeUnit.SECONDS);
+        }
+
+        @Override
+        public void onInvalidated(@NonNull Set<String> tables) {
+            mInvalidatedTables = tables;
+            mLatch.countDown();
+        }
+
+        void reset(@SuppressWarnings("SameParameterValue") int count) {
+            mInvalidatedTables = null;
+            mLatch = new CountDownLatch(count);
+        }
+
+        Set<String> getInvalidatedTables() {
+            return mInvalidatedTables;
+        }
+    }
+
+    private static void forceGc() {
+        // Use a random index in the list to detect the garbage collection each time because
+        // .get() may accidentally trigger a strong reference during collection.
+        ArrayList<WeakReference<byte[]>> leak = new ArrayList<>();
+        do {
+            WeakReference<byte[]> arr = new WeakReference<>(new byte[100]);
+            leak.add(arr);
+        } while (leak.get((int) (Math.random() * leak.size())).get() != null);
+    }
+}
diff --git a/room/runtime/src/test/java/android/arch/persistence/room/ObservedTableTrackerTest.java b/room/runtime/src/test/java/android/arch/persistence/room/ObservedTableTrackerTest.java
new file mode 100644
index 0000000..ffddee9
--- /dev/null
+++ b/room/runtime/src/test/java/android/arch/persistence/room/ObservedTableTrackerTest.java
@@ -0,0 +1,108 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+
+package android.arch.persistence.room;
+
+
+import static android.arch.persistence.room.InvalidationTracker.ObservedTableTracker.ADD;
+import static android.arch.persistence.room.InvalidationTracker.ObservedTableTracker.NO_OP;
+import static android.arch.persistence.room.InvalidationTracker.ObservedTableTracker.REMOVE;
+
+import static org.hamcrest.CoreMatchers.is;
+import static org.hamcrest.CoreMatchers.nullValue;
+import static org.hamcrest.MatcherAssert.assertThat;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+
+import java.util.Arrays;
+
+@RunWith(JUnit4.class)
+public class ObservedTableTrackerTest {
+    private static final int TABLE_COUNT = 5;
+    private InvalidationTracker.ObservedTableTracker mTracker;
+
+    @Before
+    public void setup() {
+        mTracker = new InvalidationTracker.ObservedTableTracker(TABLE_COUNT);
+    }
+
+    @Test
+    public void basicAdd() {
+        mTracker.onAdded(2, 3);
+        assertThat(mTracker.getTablesToSync(), is(createResponse(2, ADD, 3, ADD)));
+    }
+
+    @Test
+    public void basicRemove() {
+        initState(2, 3);
+        mTracker.onRemoved(3);
+        assertThat(mTracker.getTablesToSync(), is(createResponse(3, REMOVE)));
+    }
+
+    @Test
+    public void noChange() {
+        initState(1, 3);
+        mTracker.onAdded(3);
+        assertThat(mTracker.getTablesToSync(), is(nullValue()));
+    }
+
+    @Test
+    public void returnNullUntilSync() {
+        initState(1, 3);
+        mTracker.onAdded(4);
+        assertThat(mTracker.getTablesToSync(), is(createResponse(4, ADD)));
+        mTracker.onAdded(0);
+        assertThat(mTracker.getTablesToSync(), is(nullValue()));
+        mTracker.onSyncCompleted();
+        assertThat(mTracker.getTablesToSync(), is(createResponse(0, ADD)));
+    }
+
+    @Test
+    public void multipleAdditionsDeletions() {
+        initState(2, 4);
+        mTracker.onAdded(2);
+        assertThat(mTracker.getTablesToSync(), is(nullValue()));
+        mTracker.onAdded(2, 4);
+        assertThat(mTracker.getTablesToSync(), is(nullValue()));
+        mTracker.onRemoved(2);
+        assertThat(mTracker.getTablesToSync(), is(nullValue()));
+        mTracker.onRemoved(2, 4);
+        assertThat(mTracker.getTablesToSync(), is(nullValue()));
+        mTracker.onAdded(1, 3);
+        mTracker.onRemoved(2, 4);
+        assertThat(mTracker.getTablesToSync(), is(
+                createResponse(1, ADD, 2, REMOVE, 3, ADD, 4, REMOVE)));
+    }
+
+    private void initState(int... tableIds) {
+        mTracker.onAdded(tableIds);
+        mTracker.getTablesToSync();
+        mTracker.onSyncCompleted();
+    }
+
+    private static int[] createResponse(int... tuples) {
+        int[] result = new int[TABLE_COUNT];
+        Arrays.fill(result, NO_OP);
+        for (int i = 0; i < tuples.length; i += 2) {
+            result[tuples[i]] = tuples[i + 1];
+        }
+        return result;
+    }
+}
diff --git a/room/runtime/src/test/java/android/arch/persistence/room/RoomSQLiteQueryTest.java b/room/runtime/src/test/java/android/arch/persistence/room/RoomSQLiteQueryTest.java
new file mode 100644
index 0000000..e7a8644
--- /dev/null
+++ b/room/runtime/src/test/java/android/arch/persistence/room/RoomSQLiteQueryTest.java
@@ -0,0 +1,149 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.arch.persistence.room;
+
+import static org.hamcrest.CoreMatchers.is;
+import static org.hamcrest.CoreMatchers.not;
+import static org.hamcrest.CoreMatchers.sameInstance;
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.verify;
+
+import android.arch.persistence.db.SupportSQLiteProgram;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+
+import java.util.ArrayList;
+import java.util.Iterator;
+import java.util.List;
+
+@RunWith(JUnit4.class)
+public class RoomSQLiteQueryTest {
+    @Before
+    public void clear() {
+        RoomSQLiteQuery.sQueryPool.clear();
+    }
+
+    @Test
+    public void acquireBasic() {
+        RoomSQLiteQuery query = RoomSQLiteQuery.acquire("abc", 3);
+        assertThat(query.getSql(), is("abc"));
+        assertThat(query.mArgCount, is(3));
+        assertThat(query.mBlobBindings.length, is(4));
+        assertThat(query.mLongBindings.length, is(4));
+        assertThat(query.mStringBindings.length, is(4));
+        assertThat(query.mDoubleBindings.length, is(4));
+    }
+
+    @Test
+    public void acquireSameSizeAgain() {
+        RoomSQLiteQuery query = RoomSQLiteQuery.acquire("abc", 3);
+        query.release();
+        assertThat(RoomSQLiteQuery.acquire("blah", 3), sameInstance(query));
+    }
+
+    @Test
+    public void acquireSameSizeWithoutRelease() {
+        RoomSQLiteQuery query = RoomSQLiteQuery.acquire("abc", 3);
+        assertThat(RoomSQLiteQuery.acquire("fda", 3), not(sameInstance(query)));
+    }
+
+    @Test
+    public void bindings() {
+        RoomSQLiteQuery query = RoomSQLiteQuery.acquire("abc", 6);
+        byte[] myBlob = new byte[3];
+        long myLong = 3L;
+        double myDouble = 7.0;
+        String myString = "ss";
+        query.bindBlob(1, myBlob);
+        query.bindLong(2, myLong);
+        query.bindNull(3);
+        query.bindDouble(4, myDouble);
+        query.bindString(5, myString);
+        query.bindNull(6);
+        SupportSQLiteProgram program = mock(SupportSQLiteProgram.class);
+        query.bindTo(program);
+
+        verify(program).bindBlob(1, myBlob);
+        verify(program).bindLong(2, myLong);
+        verify(program).bindNull(3);
+        verify(program).bindDouble(4, myDouble);
+        verify(program).bindString(5, myString);
+        verify(program).bindNull(6);
+    }
+
+    @Test
+    public void dontKeepSameSizeTwice() {
+        RoomSQLiteQuery query1 = RoomSQLiteQuery.acquire("abc", 3);
+        RoomSQLiteQuery query2 = RoomSQLiteQuery.acquire("zx", 3);
+        RoomSQLiteQuery query3 = RoomSQLiteQuery.acquire("qw", 0);
+
+        query1.release();
+        query2.release();
+        assertThat(RoomSQLiteQuery.sQueryPool.size(), is(1));
+
+        query3.release();
+        assertThat(RoomSQLiteQuery.sQueryPool.size(), is(2));
+    }
+
+    @Test
+    public void returnExistingForSmallerSize() {
+        RoomSQLiteQuery query = RoomSQLiteQuery.acquire("abc", 3);
+        query.release();
+        assertThat(RoomSQLiteQuery.acquire("dsa", 2), sameInstance(query));
+    }
+
+    @Test
+    public void returnNewForBigger() {
+        RoomSQLiteQuery query = RoomSQLiteQuery.acquire("abc", 3);
+        query.release();
+        assertThat(RoomSQLiteQuery.acquire("dsa", 4), not(sameInstance(query)));
+    }
+
+    @Test
+    public void pruneCache() {
+        for (int i = 0; i < RoomSQLiteQuery.POOL_LIMIT; i++) {
+            RoomSQLiteQuery.acquire("dsdsa", i).release();
+        }
+        pruneCacheTest();
+    }
+
+    @Test
+    public void pruneCacheReverseInsertion() {
+        List<RoomSQLiteQuery> queries = new ArrayList<>();
+        for (int i = RoomSQLiteQuery.POOL_LIMIT - 1; i >= 0; i--) {
+            queries.add(RoomSQLiteQuery.acquire("dsdsa", i));
+        }
+        for (RoomSQLiteQuery query : queries) {
+            query.release();
+        }
+        pruneCacheTest();
+    }
+
+    private void pruneCacheTest() {
+        assertThat(RoomSQLiteQuery.sQueryPool.size(), is(RoomSQLiteQuery.POOL_LIMIT));
+        RoomSQLiteQuery.acquire("dsadsa", RoomSQLiteQuery.POOL_LIMIT + 1).release();
+        assertThat(RoomSQLiteQuery.sQueryPool.size(), is(RoomSQLiteQuery.DESIRED_POOL_SIZE));
+        Iterator<RoomSQLiteQuery> itr = RoomSQLiteQuery.sQueryPool.values().iterator();
+        for (int i = 0; i < RoomSQLiteQuery.DESIRED_POOL_SIZE; i++) {
+            assertThat(itr.next().mCapacity, is(i));
+        }
+    }
+}
diff --git a/room/runtime/src/test/java/android/arch/persistence/room/SharedSQLiteStatementTest.java b/room/runtime/src/test/java/android/arch/persistence/room/SharedSQLiteStatementTest.java
new file mode 100644
index 0000000..4e715e9
--- /dev/null
+++ b/room/runtime/src/test/java/android/arch/persistence/room/SharedSQLiteStatementTest.java
@@ -0,0 +1,126 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package android.arch.persistence.room;
+
+import static org.hamcrest.CoreMatchers.is;
+import static org.hamcrest.CoreMatchers.not;
+import static org.hamcrest.CoreMatchers.notNullValue;
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.mockito.Matchers.anyString;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import android.arch.persistence.db.SupportSQLiteStatement;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+import org.mockito.invocation.InvocationOnMock;
+import org.mockito.stubbing.Answer;
+
+import java.util.concurrent.Callable;
+import java.util.concurrent.ExecutionException;
+import java.util.concurrent.FutureTask;
+
+@RunWith(JUnit4.class)
+public class SharedSQLiteStatementTest {
+    private SharedSQLiteStatement mSharedStmt;
+    RoomDatabase mDb;
+    @Before
+    public void init() {
+        mDb = mock(RoomDatabase.class);
+        when(mDb.compileStatement(anyString())).thenAnswer(new Answer<SupportSQLiteStatement>() {
+
+            @Override
+            public SupportSQLiteStatement answer(InvocationOnMock invocation) throws Throwable {
+                return mock(SupportSQLiteStatement.class);
+            }
+        });
+        when(mDb.getInvalidationTracker()).thenReturn(mock(InvalidationTracker.class));
+        mSharedStmt = new SharedSQLiteStatement(mDb) {
+            @Override
+            protected String createQuery() {
+                return "foo";
+            }
+        };
+    }
+
+    @Test
+    public void checkMainThread() {
+        mSharedStmt.acquire();
+        verify(mDb).assertNotMainThread();
+    }
+
+    @Test
+    public void basic() {
+        assertThat(mSharedStmt.acquire(), notNullValue());
+    }
+
+    @Test
+    public void getTwiceWithoutReleasing() {
+        SupportSQLiteStatement stmt1 = mSharedStmt.acquire();
+        SupportSQLiteStatement stmt2 = mSharedStmt.acquire();
+        assertThat(stmt1, notNullValue());
+        assertThat(stmt2, notNullValue());
+        assertThat(stmt1, is(not(stmt2)));
+    }
+
+    @Test
+    public void getTwiceWithReleasing() {
+        SupportSQLiteStatement stmt1 = mSharedStmt.acquire();
+        mSharedStmt.release(stmt1);
+        SupportSQLiteStatement stmt2 = mSharedStmt.acquire();
+        assertThat(stmt1, notNullValue());
+        assertThat(stmt1, is(stmt2));
+    }
+
+    @Test
+    public void getFromAnotherThreadWhileHolding() throws ExecutionException, InterruptedException {
+        SupportSQLiteStatement stmt1 = mSharedStmt.acquire();
+        FutureTask<SupportSQLiteStatement> task = new FutureTask<>(
+                new Callable<SupportSQLiteStatement>() {
+                    @Override
+                    public SupportSQLiteStatement call() throws Exception {
+                        return mSharedStmt.acquire();
+                    }
+                });
+        new Thread(task).run();
+        SupportSQLiteStatement stmt2 = task.get();
+        assertThat(stmt1, notNullValue());
+        assertThat(stmt2, notNullValue());
+        assertThat(stmt1, is(not(stmt2)));
+    }
+
+    @Test
+    public void getFromAnotherThreadAfterReleasing() throws ExecutionException,
+            InterruptedException {
+        SupportSQLiteStatement stmt1 = mSharedStmt.acquire();
+        mSharedStmt.release(stmt1);
+        FutureTask<SupportSQLiteStatement> task = new FutureTask<>(
+                new Callable<SupportSQLiteStatement>() {
+                    @Override
+                    public SupportSQLiteStatement call() throws Exception {
+                        return mSharedStmt.acquire();
+                    }
+                });
+        new Thread(task).run();
+        SupportSQLiteStatement stmt2 = task.get();
+        assertThat(stmt1, notNullValue());
+        assertThat(stmt1, is(stmt2));
+    }
+}
diff --git a/room/runtime/src/test/java/android/arch/persistence/room/util/StringUtilTest.java b/room/runtime/src/test/java/android/arch/persistence/room/util/StringUtilTest.java
new file mode 100644
index 0000000..c10fab6
--- /dev/null
+++ b/room/runtime/src/test/java/android/arch/persistence/room/util/StringUtilTest.java
@@ -0,0 +1,68 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.arch.persistence.room.util;
+
+import static org.hamcrest.CoreMatchers.is;
+import static org.hamcrest.CoreMatchers.nullValue;
+import static org.hamcrest.MatcherAssert.assertThat;
+
+import static java.util.Arrays.asList;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+
+import java.util.Collections;
+
+@SuppressWarnings("ArraysAsListWithZeroOrOneArgument")
+@RunWith(JUnit4.class)
+public class StringUtilTest {
+    @Test
+    public void testEmpty() {
+        assertThat(StringUtil.splitToIntList(""), is(Collections.<Integer>emptyList()));
+        assertThat(StringUtil.joinIntoString(Collections.<Integer>emptyList()), is(""));
+    }
+
+    @Test
+    public void testNull() {
+        assertThat(StringUtil.splitToIntList(null), nullValue());
+        assertThat(StringUtil.joinIntoString(null), nullValue());
+    }
+
+    @Test
+    public void testSingle() {
+        assertThat(StringUtil.splitToIntList("4"), is(asList(4)));
+        assertThat(StringUtil.joinIntoString(asList(4)), is("4"));
+    }
+
+    @Test
+    public void testMultiple() {
+        assertThat(StringUtil.splitToIntList("4,5"), is(asList(4, 5)));
+        assertThat(StringUtil.joinIntoString(asList(4, 5)), is("4,5"));
+    }
+
+    @Test
+    public void testNegative() {
+        assertThat(StringUtil.splitToIntList("-4,-5,6,-7"), is(asList(-4, -5, 6, -7)));
+        assertThat(StringUtil.joinIntoString(asList(-4, -5, 6, -7)), is("-4,-5,6,-7"));
+    }
+
+    @Test
+    public void ignoreMalformed() {
+        assertThat(StringUtil.splitToIntList("-4,a,5,7"), is(asList(-4, 5, 7)));
+    }
+}
diff --git a/room/rxjava2/build.gradle b/room/rxjava2/build.gradle
new file mode 100644
index 0000000..d7b5ceb
--- /dev/null
+++ b/room/rxjava2/build.gradle
@@ -0,0 +1,72 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT 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 com.android.builder.core.BuilderConstants
+apply plugin: 'com.android.library'
+apply plugin: 'maven'
+
+android {
+    compileSdkVersion tools.current_sdk
+    buildToolsVersion tools.build_tools_version
+
+    defaultConfig {
+        minSdkVersion flatfoot.min_sdk
+        targetSdkVersion tools.current_sdk
+        versionCode 1
+        versionName "1.0"
+
+        testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
+
+    }
+    buildTypes {
+        release {
+            minifyEnabled false
+            proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
+        }
+    }
+
+    testOptions {
+        unitTests.returnDefaultValues = true
+    }
+    compileOptions {
+        sourceCompatibility JavaVersion.VERSION_1_7
+        targetCompatibility JavaVersion.VERSION_1_7
+    }
+}
+
+dependencies {
+    compile project(":room:common")
+    compile project(":room:runtime")
+    compile project(":arch:runtime")
+    compile libs.support.core_utils
+    compile libs.rx_java
+    testCompile libs.junit
+    testCompile libs.mockito_core
+    testCompile project(":arch:core-testing")
+}
+
+archivesBaseName = "rxjava2"
+
+createAndroidCheckstyle(project)
+
+android.libraryVariants.all { variant ->
+    def name = variant.buildType.name
+    def suffix = name.capitalize()
+    project.tasks.create(name: "jar${suffix}", type: Jar){
+        dependsOn variant.javaCompile
+        from variant.javaCompile.destinationDir
+        destinationDir new File(project.buildDir, "libJar")
+    }
+}
diff --git a/room/rxjava2/src/main/AndroidManifest.xml b/room/rxjava2/src/main/AndroidManifest.xml
new file mode 100644
index 0000000..33279c6
--- /dev/null
+++ b/room/rxjava2/src/main/AndroidManifest.xml
@@ -0,0 +1,19 @@
+<!--
+  ~ Copyright (C) 2017 The Android Open Source Project
+  ~
+  ~ Licensed under the Apache License, Version 2.0 (the "License");
+  ~ you may not use this file except in compliance with the License.
+  ~ You may obtain a copy of the License at
+  ~
+  ~      http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing, software
+  ~ distributed under the License is distributed on an "AS IS" BASIS,
+  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  ~ See the License for the specific language governing permissions and
+  ~ limitations under the License.
+  -->
+
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+          package="android.arch.persistence.room.rxjava2">
+</manifest>
diff --git a/room/rxjava2/src/main/java/android/arch/persistence/room/RxRoom.java b/room/rxjava2/src/main/java/android/arch/persistence/room/RxRoom.java
new file mode 100644
index 0000000..adfca27
--- /dev/null
+++ b/room/rxjava2/src/main/java/android/arch/persistence/room/RxRoom.java
@@ -0,0 +1,189 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.arch.persistence.room;
+
+import android.arch.core.executor.AppToolkitTaskExecutor;
+import android.support.annotation.Nullable;
+import android.support.annotation.RestrictTo;
+
+import java.util.Set;
+import java.util.concurrent.Callable;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.atomic.AtomicBoolean;
+
+import io.reactivex.BackpressureStrategy;
+import io.reactivex.Flowable;
+import io.reactivex.FlowableEmitter;
+import io.reactivex.FlowableOnSubscribe;
+import io.reactivex.Scheduler;
+import io.reactivex.annotations.NonNull;
+import io.reactivex.disposables.Disposable;
+import io.reactivex.disposables.Disposables;
+import io.reactivex.functions.Action;
+import io.reactivex.functions.Function;
+import io.reactivex.functions.Predicate;
+
+/**
+ * Helper class to add RxJava2 support to Room.
+ */
+@SuppressWarnings("WeakerAccess")
+public class RxRoom {
+    /**
+     * Data dispatched by the publisher created by {@link #createFlowable(RoomDatabase, String...)}.
+     */
+    public static final Object NOTHING = new Object();
+
+    /**
+     * Creates a {@link Flowable} that emits at least once and also re-emits whenever one of the
+     * observed tables is updated.
+     * <p>
+     * You can easily chain a database operation to downstream of this {@link Flowable} to ensure
+     * that it re-runs when database is modified.
+     * <p>
+     * Since database invalidation is batched, multiple changes in the database may results in just
+     * 1 emission.
+     *
+     * @param database   The database instance
+     * @param tableNames The list of table names that should be observed
+     * @return A {@link Flowable} which emits {@link #NOTHING} when one of the observed tables
+     * is modified (also once when the invalidation tracker connection is established).
+     */
+    public static Flowable<Object> createFlowable(final RoomDatabase database,
+            final String... tableNames) {
+        return Flowable.create(new FlowableOnSubscribe<Object>() {
+            @Override
+            public void subscribe(final FlowableEmitter<Object> emitter) throws Exception {
+                final InvalidationTracker.Observer observer = new InvalidationTracker.Observer(
+                        tableNames) {
+                    @Override
+                    public void onInvalidated(
+                            @android.support.annotation.NonNull Set<String> tables) {
+                        if (!emitter.isCancelled()) {
+                            emitter.onNext(NOTHING);
+                        }
+                    }
+                };
+                if (!emitter.isCancelled()) {
+                    database.getInvalidationTracker().addObserver(observer);
+                    emitter.setDisposable(Disposables.fromAction(new Action() {
+                        @Override
+                        public void run() throws Exception {
+                            database.getInvalidationTracker().removeObserver(observer);
+                        }
+                    }));
+                }
+
+                // emit once to avoid missing any data and also easy chaining
+                if (!emitter.isCancelled()) {
+                    emitter.onNext(NOTHING);
+                }
+            }
+        }, BackpressureStrategy.LATEST);
+    }
+
+    /**
+     * Helper method used by generated code to bind a Callable such that it will be run in
+     * our disk io thread and will automatically block null values since RxJava2 does not like null.
+     *
+     * @hide
+     */
+    @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
+    public static <T> Flowable<T> createFlowable(final RoomDatabase database,
+            final String[] tableNames, final Callable<T> callable) {
+        return createFlowable(database, tableNames).observeOn(sAppToolkitIOScheduler)
+                .map(new Function<Object, Optional<T>>() {
+                    @Override
+                    public Optional<T> apply(@NonNull Object o) throws Exception {
+                        T data = callable.call();
+                        return new Optional<>(data);
+                    }
+                }).filter(new Predicate<Optional<T>>() {
+                    @Override
+                    public boolean test(@NonNull Optional<T> optional) throws Exception {
+                        return optional.mValue != null;
+                    }
+                }).map(new Function<Optional<T>, T>() {
+                    @Override
+                    public T apply(@NonNull Optional<T> optional) throws Exception {
+                        return optional.mValue;
+                    }
+                });
+    }
+
+    private static Scheduler sAppToolkitIOScheduler = new Scheduler() {
+        @Override
+        public Worker createWorker() {
+            final AtomicBoolean mDisposed = new AtomicBoolean(false);
+            return new Worker() {
+                @Override
+                public Disposable schedule(@NonNull Runnable run, long delay,
+                        @NonNull TimeUnit unit) {
+                    DisposableRunnable disposable = new DisposableRunnable(run, mDisposed);
+                    AppToolkitTaskExecutor.getInstance().executeOnDiskIO(run);
+                    return disposable;
+                }
+
+                @Override
+                public void dispose() {
+                    mDisposed.set(true);
+                }
+
+                @Override
+                public boolean isDisposed() {
+                    return mDisposed.get();
+                }
+            };
+        }
+    };
+
+    private static class DisposableRunnable implements Disposable, Runnable {
+        private final Runnable mActual;
+        private volatile boolean mDisposed = false;
+        private final AtomicBoolean mGlobalDisposed;
+
+        DisposableRunnable(Runnable actual, AtomicBoolean globalDisposed) {
+            mActual = actual;
+            mGlobalDisposed = globalDisposed;
+        }
+
+        @Override
+        public void dispose() {
+            mDisposed = true;
+        }
+
+        @Override
+        public boolean isDisposed() {
+            return mDisposed || mGlobalDisposed.get();
+        }
+
+        @Override
+        public void run() {
+            if (!isDisposed()) {
+                mActual.run();
+            }
+        }
+    }
+
+    static class Optional<T> {
+        @Nullable
+        final T mValue;
+
+        Optional(@Nullable T value) {
+            this.mValue = value;
+        }
+    }
+}
diff --git a/room/rxjava2/src/test/java/android/arch/persistence/room/RxRoomTest.java b/room/rxjava2/src/test/java/android/arch/persistence/room/RxRoomTest.java
new file mode 100644
index 0000000..502eaa1
--- /dev/null
+++ b/room/rxjava2/src/test/java/android/arch/persistence/room/RxRoomTest.java
@@ -0,0 +1,179 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.arch.persistence.room;
+
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.mockito.Matchers.any;
+import static org.mockito.Mockito.doAnswer;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import android.arch.core.executor.JunitTaskExecutorRule;
+
+import org.hamcrest.CoreMatchers;
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+import org.mockito.invocation.InvocationOnMock;
+import org.mockito.stubbing.Answer;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Set;
+import java.util.concurrent.Callable;
+import java.util.concurrent.atomic.AtomicReference;
+
+import io.reactivex.Flowable;
+import io.reactivex.annotations.NonNull;
+import io.reactivex.disposables.Disposable;
+import io.reactivex.functions.Consumer;
+import io.reactivex.subscribers.TestSubscriber;
+
+@RunWith(JUnit4.class)
+public class RxRoomTest {
+    @Rule
+    public JunitTaskExecutorRule mExecutor = new JunitTaskExecutorRule(1, false);
+    private RoomDatabase mDatabase;
+    private InvalidationTracker mInvalidationTracker;
+    private List<InvalidationTracker.Observer> mAddedObservers = new ArrayList<>();
+
+    @Before
+    public void init() {
+        mDatabase = mock(RoomDatabase.class);
+        mInvalidationTracker = mock(InvalidationTracker.class);
+        when(mDatabase.getInvalidationTracker()).thenReturn(mInvalidationTracker);
+        doAnswer(new Answer() {
+            @Override
+            public Object answer(InvocationOnMock invocation) throws Throwable {
+                mAddedObservers.add((InvalidationTracker.Observer) invocation.getArguments()[0]);
+                return null;
+            }
+        }).when(mInvalidationTracker).addObserver(any(InvalidationTracker.Observer.class));
+    }
+
+    @Test
+    public void basicAddRemove() {
+        Flowable<Object> flowable = RxRoom.createFlowable(mDatabase, "a", "b");
+        verify(mInvalidationTracker, never()).addObserver(any(InvalidationTracker.Observer.class));
+        Disposable disposable = flowable.subscribe();
+        verify(mInvalidationTracker).addObserver(any(InvalidationTracker.Observer.class));
+        assertThat(mAddedObservers.size(), CoreMatchers.is(1));
+
+        InvalidationTracker.Observer observer = mAddedObservers.get(0);
+        disposable.dispose();
+
+        verify(mInvalidationTracker).removeObserver(observer);
+
+        disposable = flowable.subscribe();
+        verify(mInvalidationTracker, times(2))
+                .addObserver(any(InvalidationTracker.Observer.class));
+        assertThat(mAddedObservers.size(), CoreMatchers.is(2));
+        assertThat(mAddedObservers.get(1), CoreMatchers.not(CoreMatchers.sameInstance(observer)));
+        InvalidationTracker.Observer observer2 = mAddedObservers.get(1);
+        disposable.dispose();
+        verify(mInvalidationTracker).removeObserver(observer2);
+    }
+
+    @Test
+    public void basicNotify() throws InterruptedException {
+        String[] tables = {"a", "b"};
+        Set<String> tableSet = new HashSet<>(Arrays.asList(tables));
+        Flowable<Object> flowable = RxRoom.createFlowable(mDatabase, tables);
+        CountingConsumer consumer = new CountingConsumer();
+        Disposable disposable = flowable.subscribe(consumer);
+        assertThat(mAddedObservers.size(), CoreMatchers.is(1));
+        InvalidationTracker.Observer observer = mAddedObservers.get(0);
+        assertThat(consumer.mCount, CoreMatchers.is(1));
+        observer.onInvalidated(tableSet);
+        assertThat(consumer.mCount, CoreMatchers.is(2));
+        observer.onInvalidated(tableSet);
+        assertThat(consumer.mCount, CoreMatchers.is(3));
+        disposable.dispose();
+        observer.onInvalidated(tableSet);
+        assertThat(consumer.mCount, CoreMatchers.is(3));
+    }
+
+    @Test
+    public void internalCallable() throws InterruptedException {
+        final AtomicReference<String> value = new AtomicReference<>(null);
+        String[] tables = {"a", "b"};
+        Set<String> tableSet = new HashSet<>(Arrays.asList(tables));
+        final Flowable<String> flowable = RxRoom.createFlowable(mDatabase, tables,
+                new Callable<String>() {
+                    @Override
+                    public String call() throws Exception {
+                        return value.get();
+                    }
+                });
+        final CountingConsumer consumer = new CountingConsumer();
+        flowable.subscribe(consumer);
+        InvalidationTracker.Observer observer = mAddedObservers.get(0);
+        drain();
+        // no value because it is null
+        assertThat(consumer.mCount, CoreMatchers.is(0));
+        value.set("bla");
+        observer.onInvalidated(tableSet);
+        drain();
+        // get value
+        assertThat(consumer.mCount, CoreMatchers.is(1));
+        observer.onInvalidated(tableSet);
+        drain();
+        // get value
+        assertThat(consumer.mCount, CoreMatchers.is(2));
+        value.set(null);
+        observer.onInvalidated(tableSet);
+        drain();
+        // no value
+        assertThat(consumer.mCount, CoreMatchers.is(2));
+    }
+
+    private void drain() throws InterruptedException {
+        mExecutor.drainTasks(2);
+    }
+
+    @Test
+    public void exception() throws InterruptedException {
+        final Flowable<String> flowable = RxRoom.createFlowable(mDatabase, new String[]{"a"},
+                new Callable<String>() {
+                    @Override
+                    public String call() throws Exception {
+                        throw new Exception("i want exception");
+                    }
+                });
+        TestSubscriber<String> subscriber = new TestSubscriber<>();
+        flowable.subscribe(subscriber);
+        drain();
+        assertThat(subscriber.errorCount(), CoreMatchers.is(1));
+        assertThat(subscriber.errors().get(0).getMessage(), CoreMatchers.is("i want exception"));
+    }
+
+    private static class CountingConsumer implements Consumer<Object> {
+        int mCount = 0;
+
+        @Override
+        public void accept(@NonNull Object o) throws Exception {
+            mCount++;
+        }
+    }
+}
diff --git a/room/testing/build.gradle b/room/testing/build.gradle
new file mode 100644
index 0000000..d378d84
--- /dev/null
+++ b/room/testing/build.gradle
@@ -0,0 +1,72 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT 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 com.android.builder.core.BuilderConstants
+apply plugin: 'com.android.library'
+apply plugin: 'maven'
+
+android {
+    compileSdkVersion tools.current_sdk
+    buildToolsVersion tools.build_tools_version
+
+    defaultConfig {
+        minSdkVersion flatfoot.min_sdk
+        targetSdkVersion tools.current_sdk
+        versionCode 1
+        versionName "1.0"
+
+        testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
+
+    }
+    buildTypes {
+        release {
+            minifyEnabled false
+            proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
+        }
+    }
+
+    testOptions {
+        unitTests.returnDefaultValues = true
+    }
+    compileOptions {
+        sourceCompatibility JavaVersion.VERSION_1_7
+        targetCompatibility JavaVersion.VERSION_1_7
+    }
+}
+
+dependencies {
+    compile project(":room:common")
+    compile project(":room:runtime")
+    compile project(":room:db")
+    compile project(":room:db-impl")
+    compile project(":room:migration")
+    compile project(":arch:runtime")
+    compile libs.support.core_utils
+    compile libs.junit
+}
+
+archivesBaseName = "testing"
+
+createAndroidCheckstyle(project)
+
+android.libraryVariants.all { variant ->
+    def name = variant.buildType.name
+    def suffix = name.capitalize()
+    project.tasks.create(name: "jar${suffix}", type: Jar){
+        dependsOn variant.javaCompile
+        from variant.javaCompile.destinationDir
+        destinationDir new File(project.buildDir, "libJar")
+    }
+}
diff --git a/room/testing/src/main/AndroidManifest.xml b/room/testing/src/main/AndroidManifest.xml
new file mode 100644
index 0000000..594f016
--- /dev/null
+++ b/room/testing/src/main/AndroidManifest.xml
@@ -0,0 +1,19 @@
+<!--
+  ~ Copyright (C) 2017 The Android Open Source Project
+  ~
+  ~ Licensed under the Apache License, Version 2.0 (the "License");
+  ~ you may not use this file except in compliance with the License.
+  ~ You may obtain a copy of the License at
+  ~
+  ~      http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing, software
+  ~ distributed under the License is distributed on an "AS IS" BASIS,
+  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  ~ See the License for the specific language governing permissions and
+  ~ limitations under the License.
+  -->
+
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+          package="android.arch.persistence.room.testing">
+</manifest>
diff --git a/room/testing/src/main/java/android/arch/persistence/room/testing/MigrationTestHelper.java b/room/testing/src/main/java/android/arch/persistence/room/testing/MigrationTestHelper.java
new file mode 100644
index 0000000..904706e
--- /dev/null
+++ b/room/testing/src/main/java/android/arch/persistence/room/testing/MigrationTestHelper.java
@@ -0,0 +1,401 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.arch.persistence.room.testing;
+
+import android.app.Instrumentation;
+import android.arch.persistence.db.SupportSQLiteDatabase;
+import android.arch.persistence.db.SupportSQLiteOpenHelper;
+import android.arch.persistence.room.DatabaseConfiguration;
+import android.arch.persistence.room.Room;
+import android.arch.persistence.room.RoomDatabase;
+import android.arch.persistence.room.RoomOpenHelper;
+import android.arch.persistence.room.migration.Migration;
+import android.arch.persistence.room.migration.bundle.DatabaseBundle;
+import android.arch.persistence.room.migration.bundle.EntityBundle;
+import android.arch.persistence.room.migration.bundle.FieldBundle;
+import android.arch.persistence.room.migration.bundle.ForeignKeyBundle;
+import android.arch.persistence.room.migration.bundle.SchemaBundle;
+import android.arch.persistence.room.util.TableInfo;
+import android.content.Context;
+import android.database.Cursor;
+import android.util.Log;
+
+import org.junit.rules.TestWatcher;
+import org.junit.runner.Description;
+
+import java.io.File;
+import java.io.FileNotFoundException;
+import java.io.IOException;
+import java.io.InputStream;
+import java.lang.ref.WeakReference;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
+/**
+ * A class that can be used in your Instrumentation tests that can create the database in an
+ * older schema.
+ * <p>
+ * You must copy the schema json files (created by passing {@code room.schemaLocation} argument
+ * into the annotation processor) into your test assets and pass in the path for that folder into
+ * the constructor. This class will read the folder and extract the schemas from there.
+ * <pre>
+ * android {
+ *   defaultConfig {
+ *     javaCompileOptions {
+ *       annotationProcessorOptions {
+ *         arguments = ["room.schemaLocation": "$projectDir/schemas".toString()]
+ *       }
+ *     }
+ *   }
+ *   sourceSets {
+ *     androidTest.assets.srcDirs += files("$projectDir/schemas".toString())
+ *   }
+ * }
+ * </pre>
+ */
+public class MigrationTestHelper extends TestWatcher {
+    private static final String TAG = "MigrationTestHelper";
+    private final String mAssetsFolder;
+    private final SupportSQLiteOpenHelper.Factory mOpenFactory;
+    private List<WeakReference<SupportSQLiteDatabase>> mManagedDatabases = new ArrayList<>();
+    private List<WeakReference<RoomDatabase>> mManagedRoomDatabases = new ArrayList<>();
+    private boolean mTestStarted;
+    private Instrumentation mInstrumentation;
+
+    /**
+     * Creates a new migration helper. It uses the Instrumentation context to load the schema
+     * (falls back to the app resources) and the target context to create the database.
+     *
+     * @param instrumentation The instrumentation instance.
+     * @param assetsFolder    The asset folder in the assets directory.
+     */
+    public MigrationTestHelper(Instrumentation instrumentation, String assetsFolder,
+            SupportSQLiteOpenHelper.Factory openFactory) {
+        mInstrumentation = instrumentation;
+        if (assetsFolder.endsWith("/")) {
+            assetsFolder = assetsFolder.substring(0, assetsFolder.length() - 1);
+        }
+        mAssetsFolder = assetsFolder;
+        mOpenFactory = openFactory;
+    }
+
+    @Override
+    protected void starting(Description description) {
+        super.starting(description);
+        mTestStarted = true;
+    }
+
+    /**
+     * Creates the database in the given version.
+     * If the database file already exists, it tries to delete it first. If delete fails, throws
+     * an exception.
+     *
+     * @param name    The name of the database.
+     * @param version The version in which the database should be created.
+     * @return A database connection which has the schema in the requested version.
+     * @throws IOException If it cannot find the schema description in the assets folder.
+     */
+    @SuppressWarnings("SameParameterValue")
+    public SupportSQLiteDatabase createDatabase(String name, int version) throws IOException {
+        File dbPath = mInstrumentation.getTargetContext().getDatabasePath(name);
+        if (dbPath.exists()) {
+            Log.d(TAG, "deleting database file " + name);
+            if (!dbPath.delete()) {
+                throw new IllegalStateException("there is a database file and i could not delete"
+                        + " it. Make sure you don't have any open connections to that database"
+                        + " before calling this method.");
+            }
+        }
+        SchemaBundle schemaBundle = loadSchema(version);
+        RoomDatabase.MigrationContainer container = new RoomDatabase.MigrationContainer();
+        DatabaseConfiguration configuration = new DatabaseConfiguration(
+                mInstrumentation.getTargetContext(), name, mOpenFactory, container, true);
+        RoomOpenHelper roomOpenHelper = new RoomOpenHelper(configuration,
+                new CreatingDelegate(schemaBundle.getDatabase()),
+                schemaBundle.getDatabase().getIdentityHash());
+        return openDatabase(name, version, roomOpenHelper);
+    }
+
+    /**
+     * Runs the given set of migrations on the provided database.
+     * <p>
+     * It uses the same algorithm that Room uses to choose migrations so the migrations instances
+     * that are provided to this method must be sufficient to bring the database from current
+     * version to the desired version.
+     * <p>
+     * After the migration, the method validates the database schema to ensure that migration
+     * result matches the expected schema. Handling of dropped tables depends on the
+     * {@code validateDroppedTables} argument. If set to true, the verification will fail if it
+     * finds a table that is not registered in the Database. If set to false, extra tables in the
+     * database will be ignored (this is the runtime library behavior).
+     *
+     * @param name                  The database name. You must first create this database via
+     *                              {@link #createDatabase(String, int)}.
+     * @param version               The final version after applying the migrations.
+     * @param validateDroppedTables If set to true, validation will fail if the database has
+     *                              unknown
+     *                              tables.
+     * @param migrations            The list of available migrations.
+     * @throws IOException           If it cannot find the schema for {@code toVersion}.
+     * @throws IllegalStateException If the schema validation fails.
+     */
+    public SupportSQLiteDatabase runMigrationsAndValidate(String name, int version,
+            boolean validateDroppedTables, Migration... migrations) throws IOException {
+        File dbPath = mInstrumentation.getTargetContext().getDatabasePath(name);
+        if (!dbPath.exists()) {
+            throw new IllegalStateException("Cannot find the database file for " + name + ". "
+                    + "Before calling runMigrations, you must first create the database via "
+                    + "createDatabase.");
+        }
+        SchemaBundle schemaBundle = loadSchema(version);
+        RoomDatabase.MigrationContainer container = new RoomDatabase.MigrationContainer();
+        container.addMigrations(migrations);
+        DatabaseConfiguration configuration = new DatabaseConfiguration(
+                mInstrumentation.getTargetContext(), name, mOpenFactory, container, true);
+        RoomOpenHelper roomOpenHelper = new RoomOpenHelper(configuration,
+                new MigratingDelegate(schemaBundle.getDatabase(), validateDroppedTables),
+                schemaBundle.getDatabase().getIdentityHash());
+        return openDatabase(name, version, roomOpenHelper);
+    }
+
+    private SupportSQLiteDatabase openDatabase(String name, int version,
+            RoomOpenHelper roomOpenHelper) {
+        SupportSQLiteOpenHelper.Configuration config =
+                SupportSQLiteOpenHelper.Configuration
+                        .builder(mInstrumentation.getTargetContext())
+                        .callback(roomOpenHelper)
+                        .name(name)
+                        .version(version)
+                        .build();
+        SupportSQLiteDatabase db = mOpenFactory.create(config).getWritableDatabase();
+        mManagedDatabases.add(new WeakReference<>(db));
+        return db;
+    }
+
+    @Override
+    protected void finished(Description description) {
+        super.finished(description);
+        for (WeakReference<SupportSQLiteDatabase> dbRef : mManagedDatabases) {
+            SupportSQLiteDatabase db = dbRef.get();
+            if (db != null && db.isOpen()) {
+                try {
+                    db.close();
+                } catch (Throwable ignored) {
+                }
+            }
+        }
+        for (WeakReference<RoomDatabase> dbRef : mManagedRoomDatabases) {
+            final RoomDatabase roomDatabase = dbRef.get();
+            if (roomDatabase != null) {
+                roomDatabase.close();
+            }
+        }
+    }
+
+    /**
+     * Registers a database connection to be automatically closed when the test finishes.
+     * <p>
+     * This only works if {@code MigrationTestHelper} is registered as a Junit test rule via
+     * {@link org.junit.Rule Rule} annotation.
+     *
+     * @param db The database connection that should be closed after the test finishes.
+     */
+    public void closeWhenFinished(SupportSQLiteDatabase db) {
+        if (!mTestStarted) {
+            throw new IllegalStateException("You cannot register a database to be closed before"
+                    + " the test starts. Maybe you forgot to annotate MigrationTestHelper as a"
+                    + " test rule? (@Rule)");
+        }
+        mManagedDatabases.add(new WeakReference<>(db));
+    }
+
+    /**
+     * Registers a database connection to be automatically closed when the test finishes.
+     * <p>
+     * This only works if {@code MigrationTestHelper} is registered as a Junit test rule via
+     * {@link org.junit.Rule Rule} annotation.
+     *
+     * @param db The RoomDatabase instance which holds the database.
+     */
+    public void closeWhenFinished(RoomDatabase db) {
+        if (!mTestStarted) {
+            throw new IllegalStateException("You cannot register a database to be closed before"
+                    + " the test starts. Maybe you forgot to annotate MigrationTestHelper as a"
+                    + " test rule? (@Rule)");
+        }
+        mManagedRoomDatabases.add(new WeakReference<>(db));
+    }
+
+    private SchemaBundle loadSchema(int version) throws IOException {
+        try {
+            return loadSchema(mInstrumentation.getContext(), version);
+        } catch (FileNotFoundException testAssetsIOExceptions) {
+            Log.w(TAG, "Could not find the schema file in the test assets. Checking the"
+                    + " application assets");
+            try {
+                return loadSchema(mInstrumentation.getTargetContext(), version);
+            } catch (FileNotFoundException appAssetsException) {
+                // throw the test assets exception instead
+                throw new FileNotFoundException("Cannot find the schema file in the assets folder. "
+                        + "Make sure to include the exported json schemas in your test assert "
+                        + "inputs. See "
+                        + "https://developer.android.com/topic/libraries/architecture/"
+                        + "room.html#db-migration-testing for details. Missing file: "
+                        + testAssetsIOExceptions.getMessage());
+            }
+        }
+    }
+
+    private SchemaBundle loadSchema(Context context, int version) throws IOException {
+        InputStream input = context.getAssets().open(mAssetsFolder + "/" + version + ".json");
+        return SchemaBundle.deserialize(input);
+    }
+
+    private static TableInfo toTableInfo(EntityBundle entityBundle) {
+        return new TableInfo(entityBundle.getTableName(), toColumnMap(entityBundle),
+                toForeignKeys(entityBundle.getForeignKeys()));
+    }
+
+    private static Set<TableInfo.ForeignKey> toForeignKeys(
+            List<ForeignKeyBundle> bundles) {
+        if (bundles == null) {
+            return Collections.emptySet();
+        }
+        Set<TableInfo.ForeignKey> result = new HashSet<>(bundles.size());
+        for (ForeignKeyBundle bundle : bundles) {
+            result.add(new TableInfo.ForeignKey(bundle.getTable(),
+                    bundle.getOnDelete(), bundle.getOnUpdate(),
+                    bundle.getColumns(), bundle.getReferencedColumns()));
+        }
+        return result;
+    }
+
+    private static Map<String, TableInfo.Column> toColumnMap(EntityBundle entity) {
+        Map<String, TableInfo.Column> result = new HashMap<>();
+        for (FieldBundle bundle : entity.getFields()) {
+            TableInfo.Column column = toColumn(entity, bundle);
+            result.put(column.name, column);
+        }
+        return result;
+    }
+
+    private static TableInfo.Column toColumn(EntityBundle entity, FieldBundle field) {
+        return new TableInfo.Column(field.getColumnName(), field.getAffinity(),
+                findPrimaryKeyPosition(entity, field));
+    }
+
+    private static int findPrimaryKeyPosition(EntityBundle entity, FieldBundle field) {
+        List<String> columnNames = entity.getPrimaryKey().getColumnNames();
+        int i = 0;
+        for (String columnName : columnNames) {
+            i++;
+            if (field.getColumnName().equalsIgnoreCase(columnName)) {
+                return i;
+            }
+        }
+        return 0;
+    }
+
+    class MigratingDelegate extends RoomOpenHelperDelegate {
+        private final boolean mVerifyDroppedTables;
+
+        MigratingDelegate(DatabaseBundle databaseBundle, boolean verifyDroppedTables) {
+            super(databaseBundle);
+            mVerifyDroppedTables = verifyDroppedTables;
+        }
+
+        @Override
+        protected void createAllTables(SupportSQLiteDatabase database) {
+            throw new UnsupportedOperationException("Was expecting to migrate but received create."
+                    + "Make sure you have created the database first.");
+        }
+
+        @Override
+        protected void validateMigration(SupportSQLiteDatabase db) {
+            final Map<String, EntityBundle> tables = mDatabaseBundle.getEntitiesByTableName();
+            for (EntityBundle entity : tables.values()) {
+                final TableInfo expected = toTableInfo(entity);
+                final TableInfo found = TableInfo.read(db, entity.getTableName());
+                if (!expected.equals(found)) {
+                    throw new IllegalStateException(
+                            "Migration failed. expected:" + expected + " , found:" + found);
+                }
+            }
+            if (mVerifyDroppedTables) {
+                // now ensure tables that should be removed are removed.
+                Cursor cursor = db.query("SELECT name FROM sqlite_master WHERE type='table'"
+                                + " AND name NOT IN(?, ?)",
+                        new String[]{Room.MASTER_TABLE_NAME, "android_metadata"});
+                //noinspection TryFinallyCanBeTryWithResources
+                try {
+                    while (cursor.moveToNext()) {
+                        final String tableName = cursor.getString(0);
+                        if (!tables.containsKey(tableName)) {
+                            throw new IllegalStateException("Migration failed. Unexpected table "
+                                    + tableName);
+                        }
+                    }
+                } finally {
+                    cursor.close();
+                }
+            }
+        }
+    }
+
+    static class CreatingDelegate extends RoomOpenHelperDelegate {
+
+        CreatingDelegate(DatabaseBundle databaseBundle) {
+            super(databaseBundle);
+        }
+
+        @Override
+        protected void createAllTables(SupportSQLiteDatabase database) {
+            for (String query : mDatabaseBundle.buildCreateQueries()) {
+                database.execSQL(query);
+            }
+        }
+
+        @Override
+        protected void validateMigration(SupportSQLiteDatabase db) {
+            throw new UnsupportedOperationException("This open helper just creates the database but"
+                    + " it received a migration request.");
+        }
+    }
+
+    abstract static class RoomOpenHelperDelegate extends RoomOpenHelper.Delegate {
+        final DatabaseBundle mDatabaseBundle;
+
+        RoomOpenHelperDelegate(DatabaseBundle databaseBundle) {
+            mDatabaseBundle = databaseBundle;
+        }
+
+        @Override
+        protected void dropAllTables(SupportSQLiteDatabase database) {
+            throw new UnsupportedOperationException("cannot drop all tables in the test");
+        }
+
+        @Override
+        protected void onOpen(SupportSQLiteDatabase database) {
+
+        }
+    }
+}
diff --git a/samples-flatfoot/ApiReviewDemo/.gitignore b/samples-flatfoot/ApiReviewDemo/.gitignore
new file mode 100644
index 0000000..39fb081
--- /dev/null
+++ b/samples-flatfoot/ApiReviewDemo/.gitignore
@@ -0,0 +1,9 @@
+*.iml
+.gradle
+/local.properties
+/.idea/workspace.xml
+/.idea/libraries
+.DS_Store
+/build
+/captures
+.externalNativeBuild
diff --git a/samples-flatfoot/ApiReviewDemo/app/.gitignore b/samples-flatfoot/ApiReviewDemo/app/.gitignore
new file mode 100644
index 0000000..796b96d
--- /dev/null
+++ b/samples-flatfoot/ApiReviewDemo/app/.gitignore
@@ -0,0 +1 @@
+/build
diff --git a/samples-flatfoot/ApiReviewDemo/app/build.gradle b/samples-flatfoot/ApiReviewDemo/app/build.gradle
new file mode 100644
index 0000000..1134737
--- /dev/null
+++ b/samples-flatfoot/ApiReviewDemo/app/build.gradle
@@ -0,0 +1,66 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+apply plugin: 'com.android.application'
+
+android {
+    compileSdkVersion 25
+    buildToolsVersion "25.0.2"
+    defaultConfig {
+        applicationId "com.android.flatfoot.apireviewdemo"
+        minSdkVersion 16
+        targetSdkVersion 25
+        versionCode 1
+        versionName "1.0"
+        testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
+    }
+    buildTypes {
+        release {
+            minifyEnabled false
+            proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
+        }
+    }
+    compileOptions {
+        sourceCompatibility JavaVersion.VERSION_1_7
+        targetCompatibility JavaVersion.VERSION_1_7
+    }
+}
+
+dependencies {
+    compile fileTree(dir: 'libs', include: ['*.jar'])
+    androidTestCompile('com.android.support.test.espresso:espresso-core:2.0', {
+        exclude group: 'com.android.support', module: 'support-annotations'
+    })
+
+    compile 'com.android.support:support-fragment:25.2.0'
+    //noinspection GradleCompatible
+    compile 'com.android.support:appcompat-v7:25.2.0'
+    //noinspection GradleCompatible
+    compile 'com.android.support:recyclerview-v7:25.2.0'
+    //noinspection GradleCompatible
+    compile 'com.android.support:cardview-v7:25.2.0'
+
+    compile "android.arch.lifecycle:extensions:1.0-SNAPSHOT"
+    compile "android.arch.persistence.room:runtime:1.0-SNAPSHOT"
+
+    compile 'com.jakewharton.timber:timber:4.5.1'
+    compile 'com.squareup.retrofit2:retrofit:2.1.0'
+    compile 'com.squareup.retrofit2:converter-gson:2.1.0'
+
+    annotationProcessor "android.arch.lifecycle:compiler:1.0-SNAPSHOT"
+    annotationProcessor "android.arch.persistence.room:compiler:1.0-SNAPSHOT"
+    testCompile 'junit:junit:4.12'
+}
diff --git a/samples-flatfoot/ApiReviewDemo/app/proguard-rules.pro b/samples-flatfoot/ApiReviewDemo/app/proguard-rules.pro
new file mode 100644
index 0000000..9926c34
--- /dev/null
+++ b/samples-flatfoot/ApiReviewDemo/app/proguard-rules.pro
@@ -0,0 +1,17 @@
+# Add project specific ProGuard rules here.
+# By default, the flags in this file are appended to flags specified
+# in /Volumes/ssd/src/ub-supportlib-master/prebuilts/fullsdk-darwin/tools/proguard/proguard-android.txt
+# You can edit the include path and order by changing the proguardFiles
+# directive in build.gradle.
+#
+# For more details, see
+#   http://developer.android.com/guide/developing/tools/proguard.html
+
+# Add any project specific keep options here:
+
+# If your project uses WebView with JS, uncomment the following
+# and specify the fully qualified class name to the JavaScript interface
+# class:
+#-keepclassmembers class fqcn.of.javascript.interface.for.webview {
+#   public *;
+#}
diff --git a/samples-flatfoot/ApiReviewDemo/app/src/androidTest/java/com/android/flatfoot/apireviewdemo/ExampleInstrumentedTest.java b/samples-flatfoot/ApiReviewDemo/app/src/androidTest/java/com/android/flatfoot/apireviewdemo/ExampleInstrumentedTest.java
new file mode 100644
index 0000000..ce71b1e
--- /dev/null
+++ b/samples-flatfoot/ApiReviewDemo/app/src/androidTest/java/com/android/flatfoot/apireviewdemo/ExampleInstrumentedTest.java
@@ -0,0 +1,42 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES 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.flatfoot.apireviewdemo;
+
+import static org.junit.Assert.assertEquals;
+
+import android.content.Context;
+import android.support.test.InstrumentationRegistry;
+import android.support.test.runner.AndroidJUnit4;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+/**
+ * Instrumentation test, which will execute on an Android device.
+ *
+ * @see <a href="http://d.android.com/tools/testing">Testing documentation</a>
+ */
+@RunWith(AndroidJUnit4.class)
+public class ExampleInstrumentedTest {
+    @Test
+    public void useAppContext() throws Exception {
+        // Context of the app under test.
+        Context appContext = InstrumentationRegistry.getTargetContext();
+
+        assertEquals("com.android.flatfoot.apireviewdemo", appContext.getPackageName());
+    }
+}
diff --git a/samples-flatfoot/ApiReviewDemo/app/src/main/AndroidManifest.xml b/samples-flatfoot/ApiReviewDemo/app/src/main/AndroidManifest.xml
new file mode 100644
index 0000000..0addf7d
--- /dev/null
+++ b/samples-flatfoot/ApiReviewDemo/app/src/main/AndroidManifest.xml
@@ -0,0 +1,95 @@
+<!--
+  ~ Copyright (C) 2017 The Android Open Source Project
+  ~
+  ~ Licensed under the Apache License, Version 2.0 (the "License");
+  ~ you may not use this file except in compliance with the License.
+  ~ You may obtain a copy of the License at
+  ~
+  ~      http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing, software
+  ~ distributed under the License is distributed on an "AS IS" BASIS,
+  ~ WITHOUT 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.flatfoot.apireviewdemo">
+
+    <uses-permission android:name="android.permission.ACCESS_FINE_LOCATION"/>
+    <uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION"/>
+    <uses-permission android:name="android.permission.INTERNET"/>
+
+    <uses-feature android:name="android.hardware.location.gps"/>
+
+    <application
+        android:name=".DemoApplication"
+        android:allowBackup="true"
+        android:icon="@mipmap/ic_launcher"
+        android:label="@string/app_name"
+        android:supportsRtl="true"
+        android:theme="@style/AppTheme">
+
+        <activity
+            android:name=".lifecycle_01_basic.LocationActivity"
+            android:label="@string/location_sample"
+            android:theme="@android:style/Theme.DeviceDefault">
+            <intent-filter>
+                <action android:name="android.intent.action.MAIN"/>
+                <category android:name="android.intent.category.LAUNCHER"/>
+            </intent-filter>
+        </activity>
+
+        <activity
+            android:name=".lifecycle_02_livedata.LiveLocationActivity"
+            android:label="@string/live_location_sample"
+            android:theme="@android:style/Theme.DeviceDefault">
+            <intent-filter>
+                <action android:name="android.intent.action.MAIN"/>
+                <category android:name="android.intent.category.LAUNCHER"/>
+            </intent-filter>
+        </activity>
+
+        <activity
+            android:name=".lifecycle_03_viewmodel.OneAccountActivity"
+            android:label="@string/one_account_github"
+            android:theme="@android:style/Theme.DeviceDefault">
+            <intent-filter>
+                <action android:name="android.intent.action.MAIN"/>
+                <category android:name="android.intent.category.LAUNCHER"/>
+            </intent-filter>
+        </activity>
+
+        <activity
+            android:name=".full_sample_xxx.SwitchAccountsActivity"
+            android:label="@string/one_account_github"
+            android:theme="@android:style/Theme.DeviceDefault">
+            <intent-filter>
+                <action android:name="android.intent.action.MAIN"/>
+                <category android:name="android.intent.category.LAUNCHER"/>
+            </intent-filter>
+        </activity>
+
+        <activity
+            android:name=".lifecycle_04_shared_viewmodel.ShapesActivity"
+            android:label="@string/shapes_label"
+            android:theme="@android:style/Theme.DeviceDefault">
+            <intent-filter>
+                <action android:name="android.intent.action.MAIN"/>
+                <category android:name="android.intent.category.LAUNCHER"/>
+            </intent-filter>
+        </activity>
+
+        <activity
+            android:name=".exercise.NoteActivity"
+            android:theme="@android:style/Theme.DeviceDefault">
+            <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-flatfoot/ApiReviewDemo/app/src/main/java/com/android/flatfoot/apireviewdemo/DemoApplication.java b/samples-flatfoot/ApiReviewDemo/app/src/main/java/com/android/flatfoot/apireviewdemo/DemoApplication.java
new file mode 100644
index 0000000..a5b7f96
--- /dev/null
+++ b/samples-flatfoot/ApiReviewDemo/app/src/main/java/com/android/flatfoot/apireviewdemo/DemoApplication.java
@@ -0,0 +1,35 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES 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.flatfoot.apireviewdemo;
+
+import android.app.Application;
+import android.content.Context;
+
+public class DemoApplication extends Application {
+
+    private static DemoApplication sApplication;
+
+    public static Context context() {
+        return sApplication;
+    }
+
+    @Override
+    public void onCreate() {
+        super.onCreate();
+        sApplication = this;
+    }
+}
diff --git a/samples-flatfoot/ApiReviewDemo/app/src/main/java/com/android/flatfoot/apireviewdemo/common/entity/Person.java b/samples-flatfoot/ApiReviewDemo/app/src/main/java/com/android/flatfoot/apireviewdemo/common/entity/Person.java
new file mode 100644
index 0000000..b3196bc
--- /dev/null
+++ b/samples-flatfoot/ApiReviewDemo/app/src/main/java/com/android/flatfoot/apireviewdemo/common/entity/Person.java
@@ -0,0 +1,70 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES 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.flatfoot.apireviewdemo.common.entity;
+
+import android.arch.persistence.room.Entity;
+import android.arch.persistence.room.PrimaryKey;
+
+@Entity
+public class Person {
+    @PrimaryKey
+    private int id;
+    private String login;
+    private String name;
+    private String lastName;
+    private String company;
+
+    public int getId() {
+        return id;
+    }
+
+    public void setId(int id) {
+        this.id = id;
+    }
+
+    public String getName() {
+        return name;
+    }
+
+    public void setName(String name) {
+        this.name = name;
+    }
+
+    public String getLastName() {
+        return lastName;
+    }
+
+    public void setLastName(String lastName) {
+        this.lastName = lastName;
+    }
+
+    public String getCompany() {
+        return company;
+    }
+
+    public void setCompany(String company) {
+        this.company = company;
+    }
+
+    public String getLogin() {
+        return login;
+    }
+
+    public void setLogin(String login) {
+        this.login = login;
+    }
+}
diff --git a/samples-flatfoot/ApiReviewDemo/app/src/main/java/com/android/flatfoot/apireviewdemo/common/github/GithubDao.java b/samples-flatfoot/ApiReviewDemo/app/src/main/java/com/android/flatfoot/apireviewdemo/common/github/GithubDao.java
new file mode 100644
index 0000000..19283bf
--- /dev/null
+++ b/samples-flatfoot/ApiReviewDemo/app/src/main/java/com/android/flatfoot/apireviewdemo/common/github/GithubDao.java
@@ -0,0 +1,49 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES 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.flatfoot.apireviewdemo.common.github;
+
+import android.arch.lifecycle.LiveData;
+import android.arch.persistence.room.Dao;
+import android.arch.persistence.room.Insert;
+import android.arch.persistence.room.OnConflictStrategy;
+import android.arch.persistence.room.Query;
+
+import com.android.flatfoot.apireviewdemo.common.entity.Person;
+
+/**
+ * Data access object for github data table.
+ */
+@Dao
+public interface GithubDao {
+    /**
+     * Load full data for a person based on the login.
+     */
+    @Query("select * from person where login = :login")
+    LiveData<Person> getLivePerson(String login);
+
+    /**
+     * Load full data for a person based on the login.
+     */
+    @Query("select * from person where login = :login")
+    Person getPerson(String login);
+
+    /**
+     * Insert or update full data for a person.
+     */
+    @Insert(onConflict = OnConflictStrategy.REPLACE)
+    void insertOrReplacePerson(Person personData);
+}
diff --git a/samples-flatfoot/ApiReviewDemo/app/src/main/java/com/android/flatfoot/apireviewdemo/common/github/GithubDatabase.java b/samples-flatfoot/ApiReviewDemo/app/src/main/java/com/android/flatfoot/apireviewdemo/common/github/GithubDatabase.java
new file mode 100644
index 0000000..3a0dfe1
--- /dev/null
+++ b/samples-flatfoot/ApiReviewDemo/app/src/main/java/com/android/flatfoot/apireviewdemo/common/github/GithubDatabase.java
@@ -0,0 +1,33 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES 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.flatfoot.apireviewdemo.common.github;
+
+import android.arch.persistence.room.Database;
+import android.arch.persistence.room.RoomDatabase;
+
+import com.android.flatfoot.apireviewdemo.common.entity.Person;
+
+/**
+ * Database for Github entities.
+ */
+@Database(entities = {Person.class}, version = 1)
+public abstract class GithubDatabase extends RoomDatabase {
+    /**
+     * Gets the data access object.
+     */
+    public abstract GithubDao getGithubDao();
+}
diff --git a/samples-flatfoot/ApiReviewDemo/app/src/main/java/com/android/flatfoot/apireviewdemo/common/github/GithubDatabaseHelper.java b/samples-flatfoot/ApiReviewDemo/app/src/main/java/com/android/flatfoot/apireviewdemo/common/github/GithubDatabaseHelper.java
new file mode 100644
index 0000000..02d5e09
--- /dev/null
+++ b/samples-flatfoot/ApiReviewDemo/app/src/main/java/com/android/flatfoot/apireviewdemo/common/github/GithubDatabaseHelper.java
@@ -0,0 +1,56 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES 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.flatfoot.apireviewdemo.common.github;
+
+import android.arch.persistence.room.Room;
+import android.content.Context;
+
+import com.android.flatfoot.apireviewdemo.DemoApplication;
+
+/**
+ * Database helper.
+ */
+public class GithubDatabaseHelper {
+    private static GithubDatabase sInstance;
+
+    /**
+     * Gets a database instance.
+     */
+    public static synchronized GithubDatabase getDatabase() {
+        if (sInstance == null) {
+            Context context = DemoApplication.context();
+            sInstance = Room.databaseBuilder(context, GithubDatabase.class, "github.db").build();
+        }
+        return sInstance;
+    }
+
+}
diff --git a/samples-flatfoot/ApiReviewDemo/app/src/main/java/com/android/flatfoot/apireviewdemo/common/github/GithubService.java b/samples-flatfoot/ApiReviewDemo/app/src/main/java/com/android/flatfoot/apireviewdemo/common/github/GithubService.java
new file mode 100644
index 0000000..2c44097
--- /dev/null
+++ b/samples-flatfoot/ApiReviewDemo/app/src/main/java/com/android/flatfoot/apireviewdemo/common/github/GithubService.java
@@ -0,0 +1,34 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES 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.flatfoot.apireviewdemo.common.github;
+
+import com.android.flatfoot.apireviewdemo.common.entity.Person;
+
+import retrofit2.Call;
+import retrofit2.http.GET;
+import retrofit2.http.Path;
+
+/**
+ * Retrofit-powered service to connect to Github backend.
+ */
+public interface GithubService {
+    /**
+     * Gets the information about the specified user.
+     */
+    @GET("/users/{user}")
+    Call<Person> getUser(@Path("user") String user);
+}
diff --git a/samples-flatfoot/ApiReviewDemo/app/src/main/java/com/android/flatfoot/apireviewdemo/db_01_basic/BasicDatabase.java b/samples-flatfoot/ApiReviewDemo/app/src/main/java/com/android/flatfoot/apireviewdemo/db_01_basic/BasicDatabase.java
new file mode 100644
index 0000000..2bfb0d9
--- /dev/null
+++ b/samples-flatfoot/ApiReviewDemo/app/src/main/java/com/android/flatfoot/apireviewdemo/db_01_basic/BasicDatabase.java
@@ -0,0 +1,26 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES 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.flatfoot.apireviewdemo.db_01_basic;
+
+
+import android.arch.persistence.room.Database;
+import android.arch.persistence.room.RoomDatabase;
+
+@Database(entities = User.class, version = 1)
+public abstract class BasicDatabase extends RoomDatabase {
+
+}
diff --git a/samples-flatfoot/ApiReviewDemo/app/src/main/java/com/android/flatfoot/apireviewdemo/db_01_basic/Usage.java b/samples-flatfoot/ApiReviewDemo/app/src/main/java/com/android/flatfoot/apireviewdemo/db_01_basic/Usage.java
new file mode 100644
index 0000000..c2c52d5
--- /dev/null
+++ b/samples-flatfoot/ApiReviewDemo/app/src/main/java/com/android/flatfoot/apireviewdemo/db_01_basic/Usage.java
@@ -0,0 +1,46 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES 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.flatfoot.apireviewdemo.db_01_basic;
+
+
+import android.arch.persistence.db.SupportSQLiteDatabase;
+import android.arch.persistence.room.Room;
+import android.content.Context;
+import android.database.Cursor;
+
+import timber.log.Timber;
+
+public class Usage {
+    BasicDatabase mBasicDatabase;
+
+    public Usage(Context context) {
+        mBasicDatabase = Room.inMemoryDatabaseBuilder(context.getApplicationContext(),
+                BasicDatabase.class).build();
+    }
+
+    public void directSQL() {
+        SupportSQLiteDatabase db = mBasicDatabase.getOpenHelper().getWritableDatabase();
+        Cursor cursor = db.rawQuery("select * from User", new String[0]);
+        try {
+            while (cursor.moveToNext()) {
+                Timber.d("user name: %s", cursor.getString(cursor.getColumnIndex("name")));
+            }
+        } finally {
+            cursor.close();
+        }
+    }
+}
diff --git a/samples-flatfoot/ApiReviewDemo/app/src/main/java/com/android/flatfoot/apireviewdemo/db_01_basic/User.java b/samples-flatfoot/ApiReviewDemo/app/src/main/java/com/android/flatfoot/apireviewdemo/db_01_basic/User.java
new file mode 100644
index 0000000..949569a
--- /dev/null
+++ b/samples-flatfoot/ApiReviewDemo/app/src/main/java/com/android/flatfoot/apireviewdemo/db_01_basic/User.java
@@ -0,0 +1,29 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES 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.flatfoot.apireviewdemo.db_01_basic;
+
+
+import android.arch.persistence.room.Entity;
+import android.arch.persistence.room.PrimaryKey;
+
+@Entity
+public class User {
+    @PrimaryKey
+    public int id;
+    public String name;
+    public String lastName;
+}
diff --git a/samples-flatfoot/ApiReviewDemo/app/src/main/java/com/android/flatfoot/apireviewdemo/db_02_dao/AppDatabase_02.java b/samples-flatfoot/ApiReviewDemo/app/src/main/java/com/android/flatfoot/apireviewdemo/db_02_dao/AppDatabase_02.java
new file mode 100644
index 0000000..4b603af
--- /dev/null
+++ b/samples-flatfoot/ApiReviewDemo/app/src/main/java/com/android/flatfoot/apireviewdemo/db_02_dao/AppDatabase_02.java
@@ -0,0 +1,27 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES 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.flatfoot.apireviewdemo.db_02_dao;
+
+import android.arch.persistence.room.Database;
+import android.arch.persistence.room.RoomDatabase;
+
+import com.android.flatfoot.apireviewdemo.db_01_basic.User;
+
+@Database(entities = User.class, version = 2)
+public abstract class AppDatabase_02 extends RoomDatabase {
+    public abstract UserCrudDao userDao();
+}
diff --git a/samples-flatfoot/ApiReviewDemo/app/src/main/java/com/android/flatfoot/apireviewdemo/db_02_dao/Usage.java b/samples-flatfoot/ApiReviewDemo/app/src/main/java/com/android/flatfoot/apireviewdemo/db_02_dao/Usage.java
new file mode 100644
index 0000000..5075e2a
--- /dev/null
+++ b/samples-flatfoot/ApiReviewDemo/app/src/main/java/com/android/flatfoot/apireviewdemo/db_02_dao/Usage.java
@@ -0,0 +1,42 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES 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.flatfoot.apireviewdemo.db_02_dao;
+
+import android.arch.persistence.room.Room;
+import android.content.Context;
+
+import com.android.flatfoot.apireviewdemo.db_01_basic.User;
+
+import java.util.List;
+
+import timber.log.Timber;
+
+public class Usage {
+    AppDatabase_02 mBasicDatabase;
+
+    public Usage(Context context) {
+        mBasicDatabase = Room.inMemoryDatabaseBuilder(context.getApplicationContext(),
+                AppDatabase_02.class).build();
+    }
+
+    public void loadAllUsers() {
+        List<User> allUsers = mBasicDatabase.userDao().loadAllUsers();
+        for (User user : allUsers) {
+            Timber.d("user name %s", user.name);
+        }
+    }
+}
diff --git a/samples-flatfoot/ApiReviewDemo/app/src/main/java/com/android/flatfoot/apireviewdemo/db_02_dao/UserCrudDao.java b/samples-flatfoot/ApiReviewDemo/app/src/main/java/com/android/flatfoot/apireviewdemo/db_02_dao/UserCrudDao.java
new file mode 100644
index 0000000..fe367b9
--- /dev/null
+++ b/samples-flatfoot/ApiReviewDemo/app/src/main/java/com/android/flatfoot/apireviewdemo/db_02_dao/UserCrudDao.java
@@ -0,0 +1,54 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES 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.flatfoot.apireviewdemo.db_02_dao;
+
+import android.arch.persistence.room.Dao;
+import android.arch.persistence.room.Delete;
+import android.arch.persistence.room.Insert;
+import android.arch.persistence.room.OnConflictStrategy;
+import android.arch.persistence.room.Query;
+
+import com.android.flatfoot.apireviewdemo.db_01_basic.User;
+
+import java.util.List;
+
+@Dao
+public interface UserCrudDao {
+    @Query("select * from user")
+    List<User> loadAllUsers();
+
+    @Query("select * from user where id = :id")
+    User loadUserById(int id);
+
+    @Query("select * from user where name = :firstName and lastName = :lastName")
+    List<User> findByNameAndLastName(String firstName, String lastName);
+
+    @Insert
+    void insertUser(User user);
+
+    @Delete
+    void deleteUser(User user);
+
+    @Query("delete from user where name like :badName OR lastName like :badName")
+    int deleteUsersByName(String badName);
+
+    @Insert(onConflict = OnConflictStrategy.REPLACE)
+    void insertOrReplaceUsers(User... users);
+
+    @Delete
+    void deleteBothUsers(User user1, User user2);
+}
diff --git a/samples-flatfoot/ApiReviewDemo/app/src/main/java/com/android/flatfoot/apireviewdemo/db_03_entity/AppDatabase_03.java b/samples-flatfoot/ApiReviewDemo/app/src/main/java/com/android/flatfoot/apireviewdemo/db_03_entity/AppDatabase_03.java
new file mode 100644
index 0000000..934c3c5
--- /dev/null
+++ b/samples-flatfoot/ApiReviewDemo/app/src/main/java/com/android/flatfoot/apireviewdemo/db_03_entity/AppDatabase_03.java
@@ -0,0 +1,30 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES 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.flatfoot.apireviewdemo.db_03_entity;
+
+import android.arch.persistence.room.Database;
+import android.arch.persistence.room.RoomDatabase;
+
+import com.android.flatfoot.apireviewdemo.db_01_basic.User;
+import com.android.flatfoot.apireviewdemo.db_02_dao.UserCrudDao;
+
+@Database(entities = {User.class, Pet.class}, version = 3)
+public abstract class AppDatabase_03 extends RoomDatabase {
+    public abstract UserCrudDao userDao();
+
+    public abstract PetDao petDao();
+}
diff --git a/samples-flatfoot/ApiReviewDemo/app/src/main/java/com/android/flatfoot/apireviewdemo/db_03_entity/Pet.java b/samples-flatfoot/ApiReviewDemo/app/src/main/java/com/android/flatfoot/apireviewdemo/db_03_entity/Pet.java
new file mode 100644
index 0000000..a4cc40e
--- /dev/null
+++ b/samples-flatfoot/ApiReviewDemo/app/src/main/java/com/android/flatfoot/apireviewdemo/db_03_entity/Pet.java
@@ -0,0 +1,55 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES 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.flatfoot.apireviewdemo.db_03_entity;
+
+import android.arch.persistence.room.ColumnInfo;
+import android.arch.persistence.room.Entity;
+import android.arch.persistence.room.Index;
+import android.arch.persistence.room.PrimaryKey;
+
+@Entity(indices = @Index("name"))
+public class Pet {
+    @PrimaryKey
+    private int id;
+    @ColumnInfo(name = "owner_id", index = true)
+    private int ownerId;
+    private String name;
+
+    public int getId() {
+        return id;
+    }
+
+    public void setId(int id) {
+        this.id = id;
+    }
+
+    public int getOwnerId() {
+        return ownerId;
+    }
+
+    public void setOwnerId(int ownerId) {
+        this.ownerId = ownerId;
+    }
+
+    public String getName() {
+        return name;
+    }
+
+    public void setName(String name) {
+        this.name = name;
+    }
+}
diff --git a/samples-flatfoot/ApiReviewDemo/app/src/main/java/com/android/flatfoot/apireviewdemo/db_03_entity/PetDao.java b/samples-flatfoot/ApiReviewDemo/app/src/main/java/com/android/flatfoot/apireviewdemo/db_03_entity/PetDao.java
new file mode 100644
index 0000000..02ac65d
--- /dev/null
+++ b/samples-flatfoot/ApiReviewDemo/app/src/main/java/com/android/flatfoot/apireviewdemo/db_03_entity/PetDao.java
@@ -0,0 +1,28 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES 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.flatfoot.apireviewdemo.db_03_entity;
+
+import android.arch.persistence.room.Dao;
+import android.arch.persistence.room.Query;
+
+import java.util.List;
+
+@Dao
+public interface PetDao {
+    @Query("select * from Pet where owner_id = :userId")
+    List<Pet> findPetsOfUser(int userId);
+}
diff --git a/samples-flatfoot/ApiReviewDemo/app/src/main/java/com/android/flatfoot/apireviewdemo/db_04_pojo/AppDatabase_04.java b/samples-flatfoot/ApiReviewDemo/app/src/main/java/com/android/flatfoot/apireviewdemo/db_04_pojo/AppDatabase_04.java
new file mode 100644
index 0000000..c216901
--- /dev/null
+++ b/samples-flatfoot/ApiReviewDemo/app/src/main/java/com/android/flatfoot/apireviewdemo/db_04_pojo/AppDatabase_04.java
@@ -0,0 +1,28 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES 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.flatfoot.apireviewdemo.db_04_pojo;
+
+import android.arch.persistence.room.Database;
+import android.arch.persistence.room.RoomDatabase;
+
+import com.android.flatfoot.apireviewdemo.db_01_basic.User;
+import com.android.flatfoot.apireviewdemo.db_03_entity.Pet;
+
+@Database(entities = {User.class, Pet.class}, version = 4)
+public abstract class AppDatabase_04 extends RoomDatabase {
+    public abstract UserPetDao userPetDao();
+}
diff --git a/samples-flatfoot/ApiReviewDemo/app/src/main/java/com/android/flatfoot/apireviewdemo/db_04_pojo/UserNameAndPetName.java b/samples-flatfoot/ApiReviewDemo/app/src/main/java/com/android/flatfoot/apireviewdemo/db_04_pojo/UserNameAndPetName.java
new file mode 100644
index 0000000..daae8f0
--- /dev/null
+++ b/samples-flatfoot/ApiReviewDemo/app/src/main/java/com/android/flatfoot/apireviewdemo/db_04_pojo/UserNameAndPetName.java
@@ -0,0 +1,42 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES 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.flatfoot.apireviewdemo.db_04_pojo;
+
+import android.arch.persistence.room.ColumnInfo;
+
+public class UserNameAndPetName {
+    @ColumnInfo(name = "user_name")
+    private String userName;
+    @ColumnInfo(name = "pet_name")
+    private String petName;
+
+    public String getUserName() {
+        return userName;
+    }
+
+    public void setUserName(String userName) {
+        this.userName = userName;
+    }
+
+    public String getPetName() {
+        return petName;
+    }
+
+    public void setPetName(String petName) {
+        this.petName = petName;
+    }
+}
diff --git a/samples-flatfoot/ApiReviewDemo/app/src/main/java/com/android/flatfoot/apireviewdemo/db_04_pojo/UserPetDao.java b/samples-flatfoot/ApiReviewDemo/app/src/main/java/com/android/flatfoot/apireviewdemo/db_04_pojo/UserPetDao.java
new file mode 100644
index 0000000..9ae8e7b
--- /dev/null
+++ b/samples-flatfoot/ApiReviewDemo/app/src/main/java/com/android/flatfoot/apireviewdemo/db_04_pojo/UserPetDao.java
@@ -0,0 +1,32 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES 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.flatfoot.apireviewdemo.db_04_pojo;
+
+import android.arch.persistence.room.Dao;
+import android.arch.persistence.room.Query;
+
+import java.util.List;
+
+@Dao
+public interface UserPetDao {
+    @Query("select user.*, COUNT(*) from user, pet where user.id = pet.owner_id GROUP BY user.id")
+    List<UserWithPetCount> getUsersAndNumberOfPets();
+
+    @Query("select user.name as user_name, pet.name as pet_name FROM user, pet WHERE"
+            + " user.id = pet.owner_id")
+    List<UserNameAndPetName> getNameTuples();
+}
diff --git a/samples-flatfoot/ApiReviewDemo/app/src/main/java/com/android/flatfoot/apireviewdemo/db_04_pojo/UserWithPetCount.java b/samples-flatfoot/ApiReviewDemo/app/src/main/java/com/android/flatfoot/apireviewdemo/db_04_pojo/UserWithPetCount.java
new file mode 100644
index 0000000..62ce989
--- /dev/null
+++ b/samples-flatfoot/ApiReviewDemo/app/src/main/java/com/android/flatfoot/apireviewdemo/db_04_pojo/UserWithPetCount.java
@@ -0,0 +1,26 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES 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.flatfoot.apireviewdemo.db_04_pojo;
+
+import android.arch.persistence.room.ColumnInfo;
+
+import com.android.flatfoot.apireviewdemo.db_01_basic.User;
+
+public class UserWithPetCount extends User {
+    @ColumnInfo(name = "COUNT(*)")
+    public int petCount;
+}
diff --git a/samples-flatfoot/ApiReviewDemo/app/src/main/java/com/android/flatfoot/apireviewdemo/db_05_converters/AppDatabase_05.java b/samples-flatfoot/ApiReviewDemo/app/src/main/java/com/android/flatfoot/apireviewdemo/db_05_converters/AppDatabase_05.java
new file mode 100644
index 0000000..927ac8f
--- /dev/null
+++ b/samples-flatfoot/ApiReviewDemo/app/src/main/java/com/android/flatfoot/apireviewdemo/db_05_converters/AppDatabase_05.java
@@ -0,0 +1,25 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES 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.flatfoot.apireviewdemo.db_05_converters;
+
+import android.arch.persistence.room.Database;
+import android.arch.persistence.room.RoomDatabase;
+
+@Database(entities = Game.class, version = 5)
+public abstract class AppDatabase_05 extends RoomDatabase {
+    public abstract GameDao gameDao();
+}
diff --git a/samples-flatfoot/ApiReviewDemo/app/src/main/java/com/android/flatfoot/apireviewdemo/db_05_converters/DateConverter.java b/samples-flatfoot/ApiReviewDemo/app/src/main/java/com/android/flatfoot/apireviewdemo/db_05_converters/DateConverter.java
new file mode 100644
index 0000000..933c13e
--- /dev/null
+++ b/samples-flatfoot/ApiReviewDemo/app/src/main/java/com/android/flatfoot/apireviewdemo/db_05_converters/DateConverter.java
@@ -0,0 +1,33 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES 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.flatfoot.apireviewdemo.db_05_converters;
+
+import android.arch.persistence.room.TypeConverter;
+
+import java.util.Date;
+
+public class DateConverter {
+    @TypeConverter
+    public static Date toDate(Long timestamp) {
+        return timestamp == null ? null : new Date(timestamp);
+    }
+
+    @TypeConverter
+    public static Long toTimestamp(Date date) {
+        return date == null ? null : date.getTime();
+    }
+}
diff --git a/samples-flatfoot/ApiReviewDemo/app/src/main/java/com/android/flatfoot/apireviewdemo/db_05_converters/Game.java b/samples-flatfoot/ApiReviewDemo/app/src/main/java/com/android/flatfoot/apireviewdemo/db_05_converters/Game.java
new file mode 100644
index 0000000..470861a
--- /dev/null
+++ b/samples-flatfoot/ApiReviewDemo/app/src/main/java/com/android/flatfoot/apireviewdemo/db_05_converters/Game.java
@@ -0,0 +1,73 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES 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.flatfoot.apireviewdemo.db_05_converters;
+
+import android.arch.persistence.room.Entity;
+import android.arch.persistence.room.TypeConverters;
+
+import java.util.Date;
+
+@Entity(primaryKeys = {"home", "away", "time"})
+// could be here as well @TypeConverters(DateConverter.class)
+public class Game {
+    private String home;
+    private String away;
+    @TypeConverters(DateConverter.class)
+    private Date time;
+    private int homeScore;
+    private int awayScore;
+
+    public String getHome() {
+        return home;
+    }
+
+    public void setHome(String home) {
+        this.home = home;
+    }
+
+    public String getAway() {
+        return away;
+    }
+
+    public void setAway(String away) {
+        this.away = away;
+    }
+
+    public Date getTime() {
+        return time;
+    }
+
+    public void setTime(Date time) {
+        this.time = time;
+    }
+
+    public int getHomeScore() {
+        return homeScore;
+    }
+
+    public void setHomeScore(int homeScore) {
+        this.homeScore = homeScore;
+    }
+
+    public int getAwayScore() {
+        return awayScore;
+    }
+
+    public void setAwayScore(int awayScore) {
+        this.awayScore = awayScore;
+    }
+}
diff --git a/samples-flatfoot/ApiReviewDemo/app/src/main/java/com/android/flatfoot/apireviewdemo/db_05_converters/GameDao.java b/samples-flatfoot/ApiReviewDemo/app/src/main/java/com/android/flatfoot/apireviewdemo/db_05_converters/GameDao.java
new file mode 100644
index 0000000..a1ccafa
--- /dev/null
+++ b/samples-flatfoot/ApiReviewDemo/app/src/main/java/com/android/flatfoot/apireviewdemo/db_05_converters/GameDao.java
@@ -0,0 +1,40 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES 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.flatfoot.apireviewdemo.db_05_converters;
+
+import android.arch.persistence.room.Dao;
+import android.arch.persistence.room.Query;
+import android.arch.persistence.room.TypeConverters;
+
+import java.util.Calendar;
+import java.util.Date;
+import java.util.List;
+
+@Dao
+@TypeConverters(DateConverter.class)
+public abstract class GameDao {
+    @Query("select * from Game where `time` BETWEEN :from AND :to")
+    abstract public List<Game> findGamesInRange(Date from, Date to);
+
+    public List<Game> listGamesIn1Week() {
+        Calendar calendar = Calendar.getInstance();
+        Date today = calendar.getTime();
+        calendar.set(Calendar.DATE, 7);
+        Date nextWeek = calendar.getTime();
+        return findGamesInRange(today, nextWeek);
+    }
+}
diff --git a/samples-flatfoot/ApiReviewDemo/app/src/main/java/com/android/flatfoot/apireviewdemo/db_06_decompose/Address.java b/samples-flatfoot/ApiReviewDemo/app/src/main/java/com/android/flatfoot/apireviewdemo/db_06_decompose/Address.java
new file mode 100644
index 0000000..ba3d334
--- /dev/null
+++ b/samples-flatfoot/ApiReviewDemo/app/src/main/java/com/android/flatfoot/apireviewdemo/db_06_decompose/Address.java
@@ -0,0 +1,29 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES 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.flatfoot.apireviewdemo.db_06_decompose;
+
+import android.arch.persistence.room.Embedded;
+
+/**
+ * Decomposed into {@link School}.
+ */
+public class Address {
+    public String street;
+    public String state;
+    @Embedded
+    public Location location;
+}
diff --git a/samples-flatfoot/ApiReviewDemo/app/src/main/java/com/android/flatfoot/apireviewdemo/db_06_decompose/AppDatabase_06.java b/samples-flatfoot/ApiReviewDemo/app/src/main/java/com/android/flatfoot/apireviewdemo/db_06_decompose/AppDatabase_06.java
new file mode 100644
index 0000000..8e0f495
--- /dev/null
+++ b/samples-flatfoot/ApiReviewDemo/app/src/main/java/com/android/flatfoot/apireviewdemo/db_06_decompose/AppDatabase_06.java
@@ -0,0 +1,24 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES 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.flatfoot.apireviewdemo.db_06_decompose;
+
+import android.arch.persistence.room.Database;
+import android.arch.persistence.room.RoomDatabase;
+
+@Database(entities = {School.class}, version = 6)
+public abstract class AppDatabase_06 extends RoomDatabase {
+}
diff --git a/samples-flatfoot/ApiReviewDemo/app/src/main/java/com/android/flatfoot/apireviewdemo/db_06_decompose/Location.java b/samples-flatfoot/ApiReviewDemo/app/src/main/java/com/android/flatfoot/apireviewdemo/db_06_decompose/Location.java
new file mode 100644
index 0000000..e39f110
--- /dev/null
+++ b/samples-flatfoot/ApiReviewDemo/app/src/main/java/com/android/flatfoot/apireviewdemo/db_06_decompose/Location.java
@@ -0,0 +1,41 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES 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.flatfoot.apireviewdemo.db_06_decompose;
+
+/**
+ * Decomposed into {@link Address}.
+ */
+public class Location {
+    private long latitude;
+    private long longitude;
+
+    public long getLatitude() {
+        return latitude;
+    }
+
+    public void setLatitude(long latitude) {
+        this.latitude = latitude;
+    }
+
+    public long getLongitude() {
+        return longitude;
+    }
+
+    public void setLongitude(long longitude) {
+        this.longitude = longitude;
+    }
+}
diff --git a/samples-flatfoot/ApiReviewDemo/app/src/main/java/com/android/flatfoot/apireviewdemo/db_06_decompose/School.java b/samples-flatfoot/ApiReviewDemo/app/src/main/java/com/android/flatfoot/apireviewdemo/db_06_decompose/School.java
new file mode 100644
index 0000000..3137344
--- /dev/null
+++ b/samples-flatfoot/ApiReviewDemo/app/src/main/java/com/android/flatfoot/apireviewdemo/db_06_decompose/School.java
@@ -0,0 +1,48 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES 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.flatfoot.apireviewdemo.db_06_decompose;
+
+import android.arch.persistence.room.Embedded;
+import android.arch.persistence.room.Entity;
+import android.arch.persistence.room.PrimaryKey;
+
+/**
+ * Has decomposed address which has decomposed location so will have all the fields from 3 classes.
+ */
+@Entity
+public class School {
+    @PrimaryKey(autoGenerate = true)
+    private int id;
+    @Embedded
+    private Address address;
+
+    public int getId() {
+        return id;
+    }
+
+    public void setId(int id) {
+        this.id = id;
+    }
+
+    public Address getAddress() {
+        return address;
+    }
+
+    public void setAddress(Address address) {
+        this.address = address;
+    }
+}
diff --git a/samples-flatfoot/ApiReviewDemo/app/src/main/java/com/android/flatfoot/apireviewdemo/db_06_decompose/SchoolDao.java b/samples-flatfoot/ApiReviewDemo/app/src/main/java/com/android/flatfoot/apireviewdemo/db_06_decompose/SchoolDao.java
new file mode 100644
index 0000000..6060573
--- /dev/null
+++ b/samples-flatfoot/ApiReviewDemo/app/src/main/java/com/android/flatfoot/apireviewdemo/db_06_decompose/SchoolDao.java
@@ -0,0 +1,39 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES 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.flatfoot.apireviewdemo.db_06_decompose;
+
+import android.arch.persistence.room.Dao;
+import android.arch.persistence.room.Insert;
+import android.arch.persistence.room.OnConflictStrategy;
+import android.arch.persistence.room.Query;
+
+import java.util.List;
+
+@Dao
+public interface SchoolDao {
+    @Insert(onConflict = OnConflictStrategy.REPLACE)
+    void save(School school);
+
+    @Query("SELECT * FROM school WHERE street LIKE :street")
+    List<School> findByStreetName(String street);
+
+    @Query("SELECT street, state, latitude, longitude FROM school WHERE id = ?")
+    Address findAddressOfSchool(int id);
+
+    @Query("SELECT * FROM school WHERE id = ?")
+    Location findLocaltionOfSchool(int id);
+}
diff --git a/samples-flatfoot/ApiReviewDemo/app/src/main/java/com/android/flatfoot/apireviewdemo/exercise/Note.java b/samples-flatfoot/ApiReviewDemo/app/src/main/java/com/android/flatfoot/apireviewdemo/exercise/Note.java
new file mode 100644
index 0000000..843bf60
--- /dev/null
+++ b/samples-flatfoot/ApiReviewDemo/app/src/main/java/com/android/flatfoot/apireviewdemo/exercise/Note.java
@@ -0,0 +1,53 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES 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.flatfoot.apireviewdemo.exercise;
+
+public class Note {
+    private int id;
+    private String label;
+    private String body;
+
+    public Note(int id, String label, String body) {
+        this.id = id;
+        this.label = label;
+        this.body = body;
+    }
+
+    public int getId() {
+        return id;
+    }
+
+    public void setId(int id) {
+        this.id = id;
+    }
+
+    public String getLabel() {
+        return label;
+    }
+
+    public void setLabel(String label) {
+        this.label = label;
+    }
+
+    public String getBody() {
+        return body;
+    }
+
+    public void setBody(String body) {
+        this.body = body;
+    }
+}
diff --git a/samples-flatfoot/ApiReviewDemo/app/src/main/java/com/android/flatfoot/apireviewdemo/exercise/NoteActivity.java b/samples-flatfoot/ApiReviewDemo/app/src/main/java/com/android/flatfoot/apireviewdemo/exercise/NoteActivity.java
new file mode 100644
index 0000000..847a6ec
--- /dev/null
+++ b/samples-flatfoot/ApiReviewDemo/app/src/main/java/com/android/flatfoot/apireviewdemo/exercise/NoteActivity.java
@@ -0,0 +1,50 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES 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.flatfoot.apireviewdemo.exercise;
+
+import android.arch.lifecycle.LifecycleActivity;
+import android.os.Bundle;
+import android.widget.ProgressBar;
+import android.widget.TextView;
+
+import com.android.flatfoot.apireviewdemo.R;
+
+public class NoteActivity extends LifecycleActivity {
+
+    private TextView mNoteLabelView;
+    private TextView mNoteBodyView;
+    private ProgressBar mProgressBar;
+
+    private void showNote(Note note) {
+        mNoteLabelView.setText(note.getLabel());
+        mNoteBodyView.setText(note.getBody());
+    }
+
+    @Override
+    protected void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+        setContentView(R.layout.note_activity_layout);
+        mNoteLabelView = (TextView) findViewById(R.id.note_label);
+        mNoteBodyView = (TextView) findViewById(R.id.note_body);
+        mProgressBar = (ProgressBar) findViewById(R.id.progress_bar);
+
+        //TODO complete the exercise into steps:
+        // 1. create a ViewModel and load a Note via NoteWebService
+        // 2. persist a loaded note to NoteDatabase.
+        showNote(new Note(0, "a", "b"));
+    }
+}
diff --git a/samples-flatfoot/ApiReviewDemo/app/src/main/java/com/android/flatfoot/apireviewdemo/exercise/NoteWebService.java b/samples-flatfoot/ApiReviewDemo/app/src/main/java/com/android/flatfoot/apireviewdemo/exercise/NoteWebService.java
new file mode 100644
index 0000000..6c0936f
--- /dev/null
+++ b/samples-flatfoot/ApiReviewDemo/app/src/main/java/com/android/flatfoot/apireviewdemo/exercise/NoteWebService.java
@@ -0,0 +1,72 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES 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.flatfoot.apireviewdemo.exercise;
+
+import android.support.annotation.NonNull;
+
+import java.util.Random;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.Executors;
+
+public class NoteWebService {
+
+    interface Callback {
+        void success(@NonNull Note note);
+
+        void failure(String errorMsg);
+    }
+
+    public static void loadNoteWithId(int id, Callback callback) {
+        InternalNoteWebService.load(id, callback);
+    }
+
+}
+
+// IMPLEMENTATION DETAILS
+
+class InternalNoteWebService {
+
+    static ExecutorService sService = Executors.newSingleThreadExecutor();
+    static Random sRandom = new Random(261);
+
+    private static final String[] WILDE = new String[]{
+            "I am so clever that sometimes I don't understand a single word of what I am saying",
+            "I don't want to go to heaven. None of my friends are there",
+            "I am not young enough to know everything.",
+            "Quotation is a serviceable substitute for wit."
+    };
+
+    static void load(final int id, final NoteWebService.Callback callback) {
+        sService.submit(new Runnable() {
+            @Override
+            public void run() {
+                try {
+                    Thread.sleep(5000);
+                } catch (InterruptedException e) {
+                    e.printStackTrace();
+                }
+
+                if (sRandom.nextInt(10) == 0) {
+                    callback.failure("Ooops! Something went wrong.");
+                } else {
+                    Note note = new Note(id, "Label " + id, WILDE[id % WILDE.length]);
+                    callback.success(note);
+                }
+            }
+        });
+    }
+}
\ No newline at end of file
diff --git a/samples-flatfoot/ApiReviewDemo/app/src/main/java/com/android/flatfoot/apireviewdemo/exercise/db/NoteDao.java b/samples-flatfoot/ApiReviewDemo/app/src/main/java/com/android/flatfoot/apireviewdemo/exercise/db/NoteDao.java
new file mode 100644
index 0000000..5fff92e
--- /dev/null
+++ b/samples-flatfoot/ApiReviewDemo/app/src/main/java/com/android/flatfoot/apireviewdemo/exercise/db/NoteDao.java
@@ -0,0 +1,23 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES 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.flatfoot.apireviewdemo.exercise.db;
+
+import android.arch.persistence.room.Dao;
+
+@Dao
+public class NoteDao {
+}
diff --git a/samples-flatfoot/ApiReviewDemo/app/src/main/java/com/android/flatfoot/apireviewdemo/exercise/db/NoteDatabase.java b/samples-flatfoot/ApiReviewDemo/app/src/main/java/com/android/flatfoot/apireviewdemo/exercise/db/NoteDatabase.java
new file mode 100644
index 0000000..a149498
--- /dev/null
+++ b/samples-flatfoot/ApiReviewDemo/app/src/main/java/com/android/flatfoot/apireviewdemo/exercise/db/NoteDatabase.java
@@ -0,0 +1,42 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES 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.flatfoot.apireviewdemo.exercise.db;
+
+import android.arch.persistence.room.Room;
+import android.arch.persistence.room.RoomDatabase;
+import android.content.Context;
+
+import com.android.flatfoot.apireviewdemo.DemoApplication;
+
+//@Database(entities = Note.class)
+abstract public class NoteDatabase extends RoomDatabase {
+
+    private static NoteDatabase sInstance;
+
+    public abstract NoteDao getNoteDao();
+
+    /**
+     * Gets a database instance.
+     */
+    public static synchronized NoteDatabase getInstance() {
+        if (sInstance == null) {
+            Context context = DemoApplication.context();
+            sInstance = Room.databaseBuilder(context, NoteDatabase.class, "notes.db").build();
+        }
+        return sInstance;
+    }
+}
diff --git a/samples-flatfoot/ApiReviewDemo/app/src/main/java/com/android/flatfoot/apireviewdemo/full_sample_xxx/AccountViewModel.java b/samples-flatfoot/ApiReviewDemo/app/src/main/java/com/android/flatfoot/apireviewdemo/full_sample_xxx/AccountViewModel.java
new file mode 100644
index 0000000..887a1ca
--- /dev/null
+++ b/samples-flatfoot/ApiReviewDemo/app/src/main/java/com/android/flatfoot/apireviewdemo/full_sample_xxx/AccountViewModel.java
@@ -0,0 +1,119 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES 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.flatfoot.apireviewdemo.full_sample_xxx;
+
+import android.arch.lifecycle.LiveData;
+import android.arch.lifecycle.MutableLiveData;
+import android.arch.lifecycle.Observer;
+import android.arch.lifecycle.ViewModel;
+import android.support.annotation.Nullable;
+import android.text.TextUtils;
+
+import com.android.flatfoot.apireviewdemo.common.entity.Person;
+import com.android.flatfoot.apireviewdemo.common.github.GithubDatabaseHelper;
+import com.android.flatfoot.apireviewdemo.full_sample_xxx.DataManagement.Cancelable;
+
+public class AccountViewModel extends ViewModel {
+
+    public final MutableLiveData<Status> statusData = new MutableLiveData<>();
+    public final MutableLiveData<Person> personData = new MutableLiveData<>();
+
+    private final DataManagement mDataManagement = DataManagement.getInstance();
+
+    private final DataManagement.Callback mCallback = new DataManagement.Callback() {
+        @Override
+        public void onSuccess() {
+            finishRequest(200);
+        }
+
+        @Override
+        public void onFail(int code) {
+            finishRequest(code);
+        }
+    };
+
+    private boolean inForceRefresh = false;
+    private Cancelable mCurrentRequest;
+    private String mLogin;
+    private LiveData<Person> mPrivatePersonData;
+    private Observer<Person> mObserver = new Observer<Person>() {
+        @Override
+        public void onChanged(@Nullable Person person) {
+            personData.setValue(person);
+        }
+    };
+
+    private void cancelRequest() {
+        if (mCurrentRequest != null) {
+            mCurrentRequest.cancel();
+            statusData.setValue(new Status(0, false));
+            mCurrentRequest = null;
+        }
+    }
+
+    private void finishRequest(int code) {
+        statusData.setValue(new Status(code, false));
+        inForceRefresh = false;
+        mCurrentRequest = null;
+    }
+
+    public void setUser(String login) {
+        if (TextUtils.equals(mLogin, login)) {
+            return;
+        }
+        if (mPrivatePersonData != null) {
+            mPrivatePersonData.removeObserver(mObserver);
+        }
+
+        cancelRequest();
+        mLogin = login;
+        mPrivatePersonData = GithubDatabaseHelper.getDatabase().getGithubDao().getLivePerson(
+                mLogin);
+        mPrivatePersonData.observeForever(mObserver);
+        statusData.setValue(new Status(0, true));
+        mCurrentRequest = mDataManagement.refreshIfNeeded(mLogin, mCallback);
+    }
+
+    public void forceRefresh() {
+        if (inForceRefresh || mLogin == null) {
+            return;
+        }
+        cancelRequest();
+        inForceRefresh = true;
+
+        statusData.setValue(new Status(0, true));
+        mCurrentRequest = mDataManagement.forceRefresh(mLogin, mCallback);
+    }
+
+
+    static class Status {
+        final int status;
+        final boolean updating;
+
+        Status(int status, boolean loading) {
+            this.status = status;
+            this.updating = loading;
+        }
+    }
+
+    @Override
+    protected void onCleared() {
+        if (mPrivatePersonData != null) {
+            mPrivatePersonData.removeObserver(mObserver);
+        }
+    }
+}
diff --git a/samples-flatfoot/ApiReviewDemo/app/src/main/java/com/android/flatfoot/apireviewdemo/full_sample_xxx/DataManagement.java b/samples-flatfoot/ApiReviewDemo/app/src/main/java/com/android/flatfoot/apireviewdemo/full_sample_xxx/DataManagement.java
new file mode 100644
index 0000000..de97554
--- /dev/null
+++ b/samples-flatfoot/ApiReviewDemo/app/src/main/java/com/android/flatfoot/apireviewdemo/full_sample_xxx/DataManagement.java
@@ -0,0 +1,184 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES 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.flatfoot.apireviewdemo.full_sample_xxx;
+
+import android.os.Handler;
+import android.os.Looper;
+
+import com.android.flatfoot.apireviewdemo.common.entity.Person;
+import com.android.flatfoot.apireviewdemo.common.github.GithubDao;
+import com.android.flatfoot.apireviewdemo.common.github.GithubDatabaseHelper;
+import com.android.flatfoot.apireviewdemo.common.github.GithubService;
+
+import java.io.IOException;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.Executors;
+import java.util.concurrent.Future;
+
+import retrofit2.Response;
+import retrofit2.Retrofit;
+import retrofit2.converter.gson.GsonConverterFactory;
+
+public class DataManagement {
+    private static DataManagement sInstance;
+    private static ExecutorService sExecutor = Executors.newFixedThreadPool(2);
+
+    public static DataManagement getInstance() {
+        if (sInstance == null) {
+            sInstance = new DataManagement();
+        }
+        return sInstance;
+    }
+
+    class CallbackWrapper {
+        private final Callback mCallback;
+        private final CancelRequest mCancelRequest = new CancelRequest();
+
+        CallbackWrapper(Callback callback) {
+            mCallback = callback;
+        }
+    }
+
+    interface Cancelable {
+        void cancel();
+    }
+
+    class CancelRequest implements Cancelable {
+        boolean mCanceled = false;
+        Future<?> mFuture;
+
+        @Override
+        public void cancel() {
+            mFuture.cancel(true);
+            mCanceled = true;
+        }
+    }
+
+
+    interface Callback {
+        void onSuccess();
+
+        void onFail(int code);
+    }
+
+    private Handler mHandler = new Handler(Looper.getMainLooper());
+    private final GithubService mGithubService;
+
+    public DataManagement() {
+        Retrofit retrofit = new Retrofit.Builder()
+                .baseUrl("https://api.github.com")
+                .addConverterFactory(GsonConverterFactory.create())
+                .build();
+
+        mGithubService = retrofit.create(GithubService.class);
+    }
+
+    Cancelable refreshIfNeeded(String user, Callback callback) {
+        CallbackWrapper wrapper = new CallbackWrapper(callback);
+        wrapper.mCancelRequest.mFuture = sExecutor.submit(
+                new UpdateIfNeededRunnable(user, wrapper));
+
+        return wrapper.mCancelRequest;
+    }
+
+    Cancelable forceRefresh(String user, Callback callback) {
+        CallbackWrapper wrapper = new CallbackWrapper(callback);
+        wrapper.mCancelRequest.mFuture = sExecutor.submit(new UpdateRunnable(user, wrapper));
+        return wrapper.mCancelRequest;
+    }
+
+    private class UpdateIfNeededRunnable implements Runnable {
+
+        private final String user;
+        private CallbackWrapper mCallback;
+
+        private UpdateIfNeededRunnable(String user, CallbackWrapper callback) {
+            this.user = user;
+            mCallback = callback;
+        }
+
+        @Override
+        public void run() {
+            Person personData = getGithubDao().getPerson(user);
+            if (personData == null) {
+                UpdateRunnable runnable = new UpdateRunnable(user, mCallback);
+                runnable.run();
+            } else {
+                postSuccess(mCallback);
+            }
+        }
+    }
+
+    private class UpdateRunnable implements Runnable {
+
+        private final String user;
+        private final CallbackWrapper mCallback;
+
+        private UpdateRunnable(String user, CallbackWrapper callback) {
+            this.user = user;
+            mCallback = callback;
+        }
+
+        @Override
+        public void run() {
+            try {
+                try {
+                    Thread.sleep(15000);
+                } catch (InterruptedException e) {
+                    e.printStackTrace();
+                }
+                Response<Person> response = mGithubService.getUser(this.user).execute();
+                if (response.isSuccessful()) {
+                    getGithubDao().insertOrReplacePerson(response.body());
+                    postSuccess(mCallback);
+                } else {
+                    postFail(mCallback, response.code());
+                }
+            } catch (IOException e) {
+                postFail(mCallback, -2);
+            }
+        }
+    }
+
+    private void postSuccess(final CallbackWrapper callback) {
+        mHandler.post(new Runnable() {
+            @Override
+            public void run() {
+                if (!callback.mCancelRequest.mCanceled) {
+                    callback.mCallback.onSuccess();
+                }
+            }
+        });
+    }
+
+    private void postFail(final CallbackWrapper callback, final int code) {
+        mHandler.post(new Runnable() {
+            @Override
+            public void run() {
+                if (!callback.mCancelRequest.mCanceled) {
+                    callback.mCallback.onFail(code);
+                }
+            }
+        });
+    }
+
+    private static GithubDao getGithubDao() {
+        return GithubDatabaseHelper.getDatabase().getGithubDao();
+    }
+
+
+}
diff --git a/samples-flatfoot/ApiReviewDemo/app/src/main/java/com/android/flatfoot/apireviewdemo/full_sample_xxx/SwitchAccountsActivity.java b/samples-flatfoot/ApiReviewDemo/app/src/main/java/com/android/flatfoot/apireviewdemo/full_sample_xxx/SwitchAccountsActivity.java
new file mode 100644
index 0000000..41f6b63
--- /dev/null
+++ b/samples-flatfoot/ApiReviewDemo/app/src/main/java/com/android/flatfoot/apireviewdemo/full_sample_xxx/SwitchAccountsActivity.java
@@ -0,0 +1,77 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES 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.flatfoot.apireviewdemo.full_sample_xxx;
+
+import android.arch.lifecycle.LifecycleActivity;
+import android.arch.lifecycle.Observer;
+import android.arch.lifecycle.ViewModelProviders;
+import android.os.Bundle;
+import android.support.annotation.Nullable;
+import android.view.View;
+import android.widget.TextView;
+
+import com.android.flatfoot.apireviewdemo.R;
+import com.android.flatfoot.apireviewdemo.common.entity.Person;
+
+import java.util.Random;
+
+public class SwitchAccountsActivity extends LifecycleActivity {
+
+    public final String[] USERS = new String[]{"yigit", "JakeWharton"};
+
+    @Override
+    protected void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+        final AccountViewModel viewModel = ViewModelProviders.of(this).get(AccountViewModel.class);
+        viewModel.setUser(USERS[0]);
+        setContentView(R.layout.switch_accounts);
+
+        findViewById(R.id.switch_user).setOnClickListener(new View.OnClickListener() {
+            @Override
+            public void onClick(View v) {
+                viewModel.setUser(USERS[new Random().nextInt(USERS.length)]);
+            }
+        });
+
+        viewModel.personData.observe(this, new Observer<Person>() {
+            @Override
+            public void onChanged(@Nullable Person data) {
+                if (data != null) {
+                    TextView emailView = (TextView) findViewById(R.id.name);
+                    emailView.setText(data.getName());
+                    TextView nameView = (TextView) findViewById(R.id.company);
+                    nameView.setText(data.getCompany());
+                }
+            }
+        });
+
+        viewModel.statusData.observe(this, new Observer<AccountViewModel.Status>() {
+            @Override
+            public void onChanged(AccountViewModel.Status status) {
+                findViewById(R.id.loading_spinner).setVisibility(
+                        status.updating ? View.VISIBLE : View.GONE);
+            }
+        });
+
+        findViewById(R.id.force_refresh).setOnClickListener(new View.OnClickListener() {
+            @Override
+            public void onClick(View v) {
+                viewModel.forceRefresh();
+            }
+        });
+    }
+}
diff --git a/samples-flatfoot/ApiReviewDemo/app/src/main/java/com/android/flatfoot/apireviewdemo/internal/PermissionUtils.java b/samples-flatfoot/ApiReviewDemo/app/src/main/java/com/android/flatfoot/apireviewdemo/internal/PermissionUtils.java
new file mode 100644
index 0000000..b15b646
--- /dev/null
+++ b/samples-flatfoot/ApiReviewDemo/app/src/main/java/com/android/flatfoot/apireviewdemo/internal/PermissionUtils.java
@@ -0,0 +1,45 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES 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.flatfoot.apireviewdemo.internal;
+
+import static android.Manifest.permission.ACCESS_COARSE_LOCATION;
+import static android.Manifest.permission.ACCESS_FINE_LOCATION;
+import static android.support.v4.content.ContextCompat.checkSelfPermission;
+
+import android.Manifest;
+import android.app.Activity;
+import android.content.Context;
+import android.content.pm.PackageManager;
+import android.support.v4.app.ActivityCompat;
+
+// ignore it as implementation detail
+public class PermissionUtils {
+
+    public static boolean hasLocationPermission(Context context) {
+        boolean fineLocationPermission = checkSelfPermission(context, ACCESS_FINE_LOCATION)
+                == PackageManager.PERMISSION_GRANTED;
+        boolean coarseLocationPermission = checkSelfPermission(context,
+                ACCESS_COARSE_LOCATION) == PackageManager.PERMISSION_GRANTED;
+        return fineLocationPermission && coarseLocationPermission;
+    }
+
+    public static void requestLocationPermission(Activity activity) {
+        ActivityCompat.requestPermissions(activity,
+                new String[]{Manifest.permission.ACCESS_FINE_LOCATION,
+                        Manifest.permission.ACCESS_COARSE_LOCATION}, 0);
+    }
+}
diff --git a/samples-flatfoot/ApiReviewDemo/app/src/main/java/com/android/flatfoot/apireviewdemo/internal/SimpleLocationListener.java b/samples-flatfoot/ApiReviewDemo/app/src/main/java/com/android/flatfoot/apireviewdemo/internal/SimpleLocationListener.java
new file mode 100644
index 0000000..6c364c9
--- /dev/null
+++ b/samples-flatfoot/ApiReviewDemo/app/src/main/java/com/android/flatfoot/apireviewdemo/internal/SimpleLocationListener.java
@@ -0,0 +1,35 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES 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.flatfoot.apireviewdemo.internal;
+
+import android.location.LocationListener;
+import android.os.Bundle;
+
+public abstract class SimpleLocationListener implements LocationListener {
+
+    @Override
+    public void onStatusChanged(String provider, int status, Bundle extras) {
+    }
+
+    @Override
+    public void onProviderEnabled(String provider) {
+    }
+
+    @Override
+    public void onProviderDisabled(String provider) {
+    }
+}
diff --git a/samples-flatfoot/ApiReviewDemo/app/src/main/java/com/android/flatfoot/apireviewdemo/lifecycle_01_basic/LocationActivity.java b/samples-flatfoot/ApiReviewDemo/app/src/main/java/com/android/flatfoot/apireviewdemo/lifecycle_01_basic/LocationActivity.java
new file mode 100644
index 0000000..1d59048
--- /dev/null
+++ b/samples-flatfoot/ApiReviewDemo/app/src/main/java/com/android/flatfoot/apireviewdemo/lifecycle_01_basic/LocationActivity.java
@@ -0,0 +1,65 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES 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.flatfoot.apireviewdemo.lifecycle_01_basic;
+
+import android.arch.lifecycle.LifecycleActivity;
+import android.location.Location;
+import android.os.Bundle;
+import android.support.annotation.NonNull;
+import android.widget.TextView;
+import android.widget.Toast;
+
+import com.android.flatfoot.apireviewdemo.R;
+import com.android.flatfoot.apireviewdemo.internal.PermissionUtils;
+import com.android.flatfoot.apireviewdemo.internal.SimpleLocationListener;
+
+public class LocationActivity extends LifecycleActivity {
+
+    @Override
+    public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions,
+            @NonNull int[] grantResults) {
+        super.onRequestPermissionsResult(requestCode, permissions, grantResults);
+        // ignore permission handling code as an implementation detail
+        if (PermissionUtils.hasLocationPermission(this)) {
+            startListening();
+        } else {
+            Toast.makeText(this, "This sample requires Location access", Toast.LENGTH_LONG).show();
+        }
+    }
+
+    @Override
+    protected void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+        setContentView(R.layout.location_activity);
+        // ignore permission handling code as an implementation detail
+        if (PermissionUtils.hasLocationPermission(this)) {
+            startListening();
+        } else {
+            PermissionUtils.requestLocationPermission(this);
+        }
+    }
+
+    private void startListening() {
+        LocationListener.listenLocation(this, new SimpleLocationListener() {
+            @Override
+            public void onLocationChanged(Location location) {
+                TextView textView = (TextView) findViewById(R.id.location);
+                textView.setText(location.getLatitude() + ", " + location.getLongitude());
+            }
+        });
+    }
+}
diff --git a/samples-flatfoot/ApiReviewDemo/app/src/main/java/com/android/flatfoot/apireviewdemo/lifecycle_01_basic/LocationListener.java b/samples-flatfoot/ApiReviewDemo/app/src/main/java/com/android/flatfoot/apireviewdemo/lifecycle_01_basic/LocationListener.java
new file mode 100644
index 0000000..33ececc
--- /dev/null
+++ b/samples-flatfoot/ApiReviewDemo/app/src/main/java/com/android/flatfoot/apireviewdemo/lifecycle_01_basic/LocationListener.java
@@ -0,0 +1,59 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES 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.flatfoot.apireviewdemo.lifecycle_01_basic;
+
+import android.arch.lifecycle.Lifecycle;
+import android.arch.lifecycle.LifecycleObserver;
+import android.arch.lifecycle.LifecycleOwner;
+import android.arch.lifecycle.OnLifecycleEvent;
+import android.content.Context;
+import android.location.LocationManager;
+
+import com.android.flatfoot.apireviewdemo.DemoApplication;
+import com.android.flatfoot.apireviewdemo.internal.SimpleLocationListener;
+
+@SuppressWarnings("MissingPermission")
+class LocationListener implements LifecycleObserver {
+
+    static void listenLocation(LifecycleOwner provider,
+            SimpleLocationListener listener) {
+        new LocationListener(provider, listener);
+    }
+
+    private android.location.LocationManager mLocationManager;
+    private final SimpleLocationListener mListener;
+
+    private LocationListener(LifecycleOwner provider, SimpleLocationListener listener) {
+        provider.getLifecycle().addObserver(this);
+        mListener = listener;
+    }
+
+    @OnLifecycleEvent(Lifecycle.Event.ON_START)
+    void start() {
+        mLocationManager = (LocationManager) DemoApplication.context().getSystemService(
+                Context.LOCATION_SERVICE);
+        mLocationManager.requestLocationUpdates(android.location.LocationManager.GPS_PROVIDER, 0, 0,
+                mListener);
+    }
+
+
+    @OnLifecycleEvent(Lifecycle.Event.ON_PAUSE)
+    void stop() {
+        mLocationManager.removeUpdates(mListener);
+        mLocationManager = null;
+    }
+}
diff --git a/samples-flatfoot/ApiReviewDemo/app/src/main/java/com/android/flatfoot/apireviewdemo/lifecycle_02_livedata/LiveLocationActivity.java b/samples-flatfoot/ApiReviewDemo/app/src/main/java/com/android/flatfoot/apireviewdemo/lifecycle_02_livedata/LiveLocationActivity.java
new file mode 100644
index 0000000..9af9c6e
--- /dev/null
+++ b/samples-flatfoot/ApiReviewDemo/app/src/main/java/com/android/flatfoot/apireviewdemo/lifecycle_02_livedata/LiveLocationActivity.java
@@ -0,0 +1,68 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES 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.flatfoot.apireviewdemo.lifecycle_02_livedata;
+
+import android.arch.lifecycle.LifecycleActivity;
+import android.arch.lifecycle.Observer;
+import android.location.Location;
+import android.os.Bundle;
+import android.support.annotation.NonNull;
+import android.support.annotation.Nullable;
+import android.widget.TextView;
+import android.widget.Toast;
+
+import com.android.flatfoot.apireviewdemo.R;
+import com.android.flatfoot.apireviewdemo.internal.PermissionUtils;
+
+public class LiveLocationActivity extends LifecycleActivity {
+
+    @Override
+    protected void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+        setContentView(R.layout.location_activity);
+        // ignore permission handling code as an implementation detail
+        if (PermissionUtils.hasLocationPermission(this)) {
+            startListening();
+        } else {
+            PermissionUtils.requestLocationPermission(this);
+        }
+    }
+
+    @Override
+    public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions,
+            @NonNull int[] grantResults) {
+        super.onRequestPermissionsResult(requestCode, permissions, grantResults);
+        // ignore permission handling code as an implementation detail
+        if (PermissionUtils.hasLocationPermission(this)) {
+            startListening();
+        } else {
+            Toast.makeText(this, "This sample requires a location permission",
+                    Toast.LENGTH_LONG).show();
+        }
+    }
+
+    private void startListening() {
+        LocationLiveData liveData = LocationLiveData.getInstance();
+        liveData.observe(this, new Observer<Location>() {
+            @Override
+            public void onChanged(@Nullable Location location) {
+                TextView textView = (TextView) findViewById(R.id.location);
+                textView.setText(location.getLatitude() + ", " + location.getLongitude());
+            }
+        });
+    }
+}
diff --git a/samples-flatfoot/ApiReviewDemo/app/src/main/java/com/android/flatfoot/apireviewdemo/lifecycle_02_livedata/LocationLiveData.java b/samples-flatfoot/ApiReviewDemo/app/src/main/java/com/android/flatfoot/apireviewdemo/lifecycle_02_livedata/LocationLiveData.java
new file mode 100644
index 0000000..84512c2
--- /dev/null
+++ b/samples-flatfoot/ApiReviewDemo/app/src/main/java/com/android/flatfoot/apireviewdemo/lifecycle_02_livedata/LocationLiveData.java
@@ -0,0 +1,62 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES 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.flatfoot.apireviewdemo.lifecycle_02_livedata;
+
+import android.arch.lifecycle.LiveData;
+import android.content.Context;
+import android.location.Location;
+import android.location.LocationManager;
+
+import com.android.flatfoot.apireviewdemo.DemoApplication;
+import com.android.flatfoot.apireviewdemo.internal.SimpleLocationListener;
+
+@SuppressWarnings("MissingPermission")
+public class LocationLiveData extends LiveData<Location> {
+
+    private static LocationLiveData sInstance;
+
+    public static LocationLiveData getInstance() {
+        if (sInstance == null) {
+            sInstance = new LocationLiveData();
+        }
+        return sInstance;
+    }
+
+    private LocationManager mLocationManager;
+
+    private SimpleLocationListener mListener = new SimpleLocationListener() {
+        @Override
+        public void onLocationChanged(Location location) {
+            setValue(location);
+        }
+    };
+
+    private LocationLiveData() {
+        mLocationManager = (LocationManager) DemoApplication.context().getSystemService(
+                Context.LOCATION_SERVICE);
+    }
+
+    @Override
+    protected void onActive() {
+        mLocationManager.requestLocationUpdates(LocationManager.GPS_PROVIDER, 0, 0, mListener);
+    }
+
+    @Override
+    protected void onInactive() {
+        mLocationManager.removeUpdates(mListener);
+    }
+}
diff --git a/samples-flatfoot/ApiReviewDemo/app/src/main/java/com/android/flatfoot/apireviewdemo/lifecycle_03_viewmodel/AccountViewModel.java b/samples-flatfoot/ApiReviewDemo/app/src/main/java/com/android/flatfoot/apireviewdemo/lifecycle_03_viewmodel/AccountViewModel.java
new file mode 100644
index 0000000..9cc1716
--- /dev/null
+++ b/samples-flatfoot/ApiReviewDemo/app/src/main/java/com/android/flatfoot/apireviewdemo/lifecycle_03_viewmodel/AccountViewModel.java
@@ -0,0 +1,56 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES 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.flatfoot.apireviewdemo.lifecycle_03_viewmodel;
+
+import android.arch.lifecycle.MutableLiveData;
+import android.arch.lifecycle.ViewModel;
+import android.support.annotation.NonNull;
+
+import com.android.flatfoot.apireviewdemo.common.entity.Person;
+
+public class AccountViewModel extends ViewModel {
+
+    static class PersonDataWithStatus {
+        final Person person;
+        final String errorMsg;
+        final boolean loading;
+
+        PersonDataWithStatus(Person person, String errorMsg, boolean loading) {
+            this.person = person;
+            this.errorMsg = errorMsg;
+            this.loading = loading;
+        }
+    }
+
+    public final MutableLiveData<PersonDataWithStatus> personData = new MutableLiveData<>();
+
+    public AccountViewModel() {
+        personData.setValue(new PersonDataWithStatus(null, null, true));
+        DataManagement.getInstance().requestPersonData("jakewharton",
+                new DataManagement.Callback() {
+                    @Override
+                    public void success(@NonNull Person person) {
+                        personData.setValue(new PersonDataWithStatus(person, null, false));
+                    }
+
+                    @Override
+                    public void failure(String errorMsg) {
+                        personData.setValue(new PersonDataWithStatus(null, errorMsg, false));
+                    }
+                });
+    }
+}
diff --git a/samples-flatfoot/ApiReviewDemo/app/src/main/java/com/android/flatfoot/apireviewdemo/lifecycle_03_viewmodel/DataManagement.java b/samples-flatfoot/ApiReviewDemo/app/src/main/java/com/android/flatfoot/apireviewdemo/lifecycle_03_viewmodel/DataManagement.java
new file mode 100644
index 0000000..1a1c1ba
--- /dev/null
+++ b/samples-flatfoot/ApiReviewDemo/app/src/main/java/com/android/flatfoot/apireviewdemo/lifecycle_03_viewmodel/DataManagement.java
@@ -0,0 +1,71 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES 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.flatfoot.apireviewdemo.lifecycle_03_viewmodel;
+
+import android.support.annotation.NonNull;
+
+import com.android.flatfoot.apireviewdemo.common.entity.Person;
+import com.android.flatfoot.apireviewdemo.common.github.GithubService;
+
+import retrofit2.Call;
+import retrofit2.Response;
+import retrofit2.Retrofit;
+import retrofit2.converter.gson.GsonConverterFactory;
+
+class DataManagement {
+
+    private static DataManagement sInstance = new DataManagement();
+
+    interface Callback {
+        void success(@NonNull Person person);
+
+        void failure(String errorMsg);
+    }
+
+    public static DataManagement getInstance() {
+        return sInstance;
+    }
+
+    private final GithubService mGithubService;
+
+    public DataManagement() {
+        Retrofit retrofit = new Retrofit.Builder()
+                .baseUrl("https://api.github.com")
+                .addConverterFactory(GsonConverterFactory.create())
+                .build();
+
+        mGithubService = retrofit.create(GithubService.class);
+    }
+
+    void requestPersonData(String user, final Callback callback) {
+        mGithubService.getUser(user).enqueue(new retrofit2.Callback<Person>() {
+            @Override
+            public void onResponse(Call<Person> call, Response<Person> response) {
+                if (response.isSuccessful()) {
+                    callback.success(response.body());
+                } else {
+                    callback.failure("Failed with " + response.code());
+                }
+            }
+
+            @Override
+            public void onFailure(Call<Person> call, Throwable t) {
+                callback.failure("Failed with " + t.getMessage());
+            }
+        });
+    }
+}
diff --git a/samples-flatfoot/ApiReviewDemo/app/src/main/java/com/android/flatfoot/apireviewdemo/lifecycle_03_viewmodel/OneAccountActivity.java b/samples-flatfoot/ApiReviewDemo/app/src/main/java/com/android/flatfoot/apireviewdemo/lifecycle_03_viewmodel/OneAccountActivity.java
new file mode 100644
index 0000000..c6743c7
--- /dev/null
+++ b/samples-flatfoot/ApiReviewDemo/app/src/main/java/com/android/flatfoot/apireviewdemo/lifecycle_03_viewmodel/OneAccountActivity.java
@@ -0,0 +1,51 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES 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.flatfoot.apireviewdemo.lifecycle_03_viewmodel;
+
+import android.arch.lifecycle.LifecycleActivity;
+import android.arch.lifecycle.Observer;
+import android.arch.lifecycle.ViewModelProviders;
+import android.os.Bundle;
+import android.view.View;
+import android.widget.TextView;
+
+import com.android.flatfoot.apireviewdemo.R;
+
+public class OneAccountActivity extends LifecycleActivity {
+
+    @Override
+    protected void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+        setContentView(R.layout.view_model1);
+
+        AccountViewModel viewModel = ViewModelProviders.of(this).get(AccountViewModel.class);
+        viewModel.personData.observe(this, new Observer<AccountViewModel.PersonDataWithStatus>() {
+            @Override
+            public void onChanged(AccountViewModel.PersonDataWithStatus data) {
+                if (data.person != null) {
+                    TextView emailView = (TextView) findViewById(R.id.name);
+                    emailView.setText(data.person.getName());
+                    TextView nameView = (TextView) findViewById(R.id.company);
+                    nameView.setText(data.person.getCompany());
+                }
+
+                findViewById(R.id.loading_spinner).setVisibility(data.loading ?
+                        View.VISIBLE : View.GONE);
+            }
+        });
+    }
+}
diff --git a/samples-flatfoot/ApiReviewDemo/app/src/main/java/com/android/flatfoot/apireviewdemo/lifecycle_04_shared_viewmodel/ChosenShapeFragment.java b/samples-flatfoot/ApiReviewDemo/app/src/main/java/com/android/flatfoot/apireviewdemo/lifecycle_04_shared_viewmodel/ChosenShapeFragment.java
new file mode 100644
index 0000000..cd07514
--- /dev/null
+++ b/samples-flatfoot/ApiReviewDemo/app/src/main/java/com/android/flatfoot/apireviewdemo/lifecycle_04_shared_viewmodel/ChosenShapeFragment.java
@@ -0,0 +1,60 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES 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.flatfoot.apireviewdemo.lifecycle_04_shared_viewmodel;
+
+import android.arch.lifecycle.LifecycleFragment;
+import android.arch.lifecycle.Observer;
+import android.arch.lifecycle.ViewModelProviders;
+import android.graphics.drawable.ShapeDrawable;
+import android.os.Bundle;
+import android.support.annotation.Nullable;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+
+import com.android.flatfoot.apireviewdemo.R;
+
+public class ChosenShapeFragment extends LifecycleFragment {
+
+    private View mNoneShapeView;
+    private View mShapeView;
+
+    @Nullable
+    @Override
+    public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container,
+            @Nullable Bundle savedInstanceState) {
+        View layout = inflater.inflate(R.layout.chosen_shape_layout, container);
+        mNoneShapeView = layout.findViewById(R.id.none);
+        mShapeView = layout.findViewById(R.id.color);
+        SharedViewModel viewModel = ViewModelProviders.of(getActivity()).get(SharedViewModel.class);
+        viewModel.shapeDrawableData.observe(this, new Observer<ShapeDrawable>() {
+            @Override
+            public void onChanged(@Nullable ShapeDrawable shapeDrawable) {
+                updateShape(shapeDrawable);
+            }
+        });
+        return layout;
+    }
+
+    private void updateShape(ShapeDrawable shape) {
+        mNoneShapeView.setVisibility(shape == null ? View.VISIBLE : View.GONE);
+        mShapeView.setVisibility(shape != null ? View.VISIBLE : View.GONE);
+        if (shape != null) {
+            mShapeView.setBackground(shape);
+        }
+    }
+}
diff --git a/samples-flatfoot/ApiReviewDemo/app/src/main/java/com/android/flatfoot/apireviewdemo/lifecycle_04_shared_viewmodel/ShapeChooserFragment.java b/samples-flatfoot/ApiReviewDemo/app/src/main/java/com/android/flatfoot/apireviewdemo/lifecycle_04_shared_viewmodel/ShapeChooserFragment.java
new file mode 100644
index 0000000..8a79143
--- /dev/null
+++ b/samples-flatfoot/ApiReviewDemo/app/src/main/java/com/android/flatfoot/apireviewdemo/lifecycle_04_shared_viewmodel/ShapeChooserFragment.java
@@ -0,0 +1,57 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES 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.flatfoot.apireviewdemo.lifecycle_04_shared_viewmodel;
+
+import android.arch.lifecycle.LifecycleActivity;
+import android.arch.lifecycle.LifecycleFragment;
+import android.arch.lifecycle.ViewModelProviders;
+import android.graphics.drawable.ShapeDrawable;
+import android.os.Bundle;
+import android.support.annotation.Nullable;
+import android.support.v7.widget.GridLayoutManager;
+import android.support.v7.widget.RecyclerView;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+
+import com.android.flatfoot.apireviewdemo.lifecycle_04_shared_viewmodel.internal.ShapesAdapter;
+
+public class ShapeChooserFragment extends LifecycleFragment {
+
+    @Nullable
+    @Override
+    public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container,
+            @Nullable Bundle savedInstanceState) {
+        LifecycleActivity activity = (LifecycleActivity) getActivity();
+        final SharedViewModel sharedViewModel = ViewModelProviders.of(getActivity())
+                .get(SharedViewModel.class);
+
+        RecyclerView rv = new RecyclerView(activity);
+        rv.setLayoutManager(new GridLayoutManager(activity, 3));
+
+        // adapter itself is implementation detail, ignore it
+        ShapesAdapter adapter = new ShapesAdapter();
+        adapter.setShapeListener(new ShapesAdapter.ShapeListener() {
+            @Override
+            public void onShapeChosen(ShapeDrawable shapeDrawable) {
+                sharedViewModel.shapeDrawableData.setValue(shapeDrawable);
+            }
+        });
+        rv.setAdapter(adapter);
+        return rv;
+    }
+}
diff --git a/samples-flatfoot/ApiReviewDemo/app/src/main/java/com/android/flatfoot/apireviewdemo/lifecycle_04_shared_viewmodel/ShapesActivity.java b/samples-flatfoot/ApiReviewDemo/app/src/main/java/com/android/flatfoot/apireviewdemo/lifecycle_04_shared_viewmodel/ShapesActivity.java
new file mode 100644
index 0000000..3ecfef6
--- /dev/null
+++ b/samples-flatfoot/ApiReviewDemo/app/src/main/java/com/android/flatfoot/apireviewdemo/lifecycle_04_shared_viewmodel/ShapesActivity.java
@@ -0,0 +1,31 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES 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.flatfoot.apireviewdemo.lifecycle_04_shared_viewmodel;
+
+import android.arch.lifecycle.LifecycleActivity;
+import android.os.Bundle;
+
+import com.android.flatfoot.apireviewdemo.R;
+
+public class ShapesActivity extends LifecycleActivity {
+
+    @Override
+    protected void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+        setContentView(R.layout.shape_activity);
+    }
+}
diff --git a/samples-flatfoot/ApiReviewDemo/app/src/main/java/com/android/flatfoot/apireviewdemo/lifecycle_04_shared_viewmodel/SharedViewModel.java b/samples-flatfoot/ApiReviewDemo/app/src/main/java/com/android/flatfoot/apireviewdemo/lifecycle_04_shared_viewmodel/SharedViewModel.java
new file mode 100644
index 0000000..c8768ac
--- /dev/null
+++ b/samples-flatfoot/ApiReviewDemo/app/src/main/java/com/android/flatfoot/apireviewdemo/lifecycle_04_shared_viewmodel/SharedViewModel.java
@@ -0,0 +1,31 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES 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.flatfoot.apireviewdemo.lifecycle_04_shared_viewmodel;
+
+import android.arch.lifecycle.MutableLiveData;
+import android.arch.lifecycle.ViewModel;
+import android.graphics.drawable.ShapeDrawable;
+
+public class SharedViewModel extends ViewModel {
+    public final MutableLiveData<ShapeDrawable> shapeDrawableData = createEmpty();
+
+    private static MutableLiveData<ShapeDrawable> createEmpty() {
+        MutableLiveData<ShapeDrawable> empty = new MutableLiveData<>();
+        empty.setValue(null);
+        return empty;
+    }
+}
diff --git a/samples-flatfoot/ApiReviewDemo/app/src/main/java/com/android/flatfoot/apireviewdemo/lifecycle_04_shared_viewmodel/internal/ShapesAdapter.java b/samples-flatfoot/ApiReviewDemo/app/src/main/java/com/android/flatfoot/apireviewdemo/lifecycle_04_shared_viewmodel/internal/ShapesAdapter.java
new file mode 100644
index 0000000..9713649
--- /dev/null
+++ b/samples-flatfoot/ApiReviewDemo/app/src/main/java/com/android/flatfoot/apireviewdemo/lifecycle_04_shared_viewmodel/internal/ShapesAdapter.java
@@ -0,0 +1,115 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES 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.flatfoot.apireviewdemo.lifecycle_04_shared_viewmodel.internal;
+
+
+import android.graphics.Color;
+import android.graphics.drawable.ShapeDrawable;
+import android.graphics.drawable.shapes.OvalShape;
+import android.graphics.drawable.shapes.RectShape;
+import android.graphics.drawable.shapes.RoundRectShape;
+import android.graphics.drawable.shapes.Shape;
+import android.support.v7.widget.RecyclerView;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+
+import com.android.flatfoot.apireviewdemo.R;
+
+import java.util.ArrayList;
+import java.util.List;
+
+// ignore it as an implementation detail
+public class ShapesAdapter extends RecyclerView.Adapter<VHolder> {
+
+    public interface ShapeListener {
+        void onShapeChosen(ShapeDrawable shapeDrawable);
+    }
+
+    private static final float[] RADII =
+            {16.0f, 16.0f, 16.0f, 16.0f, 16.0f, 16.0f, 16.0f, 16.0f};
+
+    private static final Shape[] SHAPES = new Shape[]{
+            new RectShape(),
+            new RoundRectShape(RADII, null, null),
+            new OvalShape(),
+    };
+
+    private static final int[] COLORS = new int[]{
+            Color.BLUE,
+            Color.CYAN,
+            Color.YELLOW,
+            Color.RED,
+            Color.GREEN,
+            Color.LTGRAY,
+            Color.MAGENTA,
+    };
+
+    private final List<ShapeDrawable> mDrawables = new ArrayList<>();
+    private ShapeListener mShapeListener;
+
+    public ShapesAdapter() {
+        for (int color : COLORS) {
+            for (Shape shape : SHAPES) {
+                ShapeDrawable drawable = new ShapeDrawable(shape);
+                drawable.getPaint().setColor(color);
+                mDrawables.add(drawable);
+            }
+        }
+    }
+
+    @Override
+    public VHolder onCreateViewHolder(ViewGroup parent, int viewType) {
+        View view = LayoutInflater.from(parent.getContext()).inflate(R.layout.shape_item,
+                parent, false);
+        final VHolder holder = new VHolder(view);
+        holder.mShapeView.setOnClickListener(new View.OnClickListener() {
+            @Override
+            public void onClick(View v) {
+                if (mShapeListener != null) {
+                    mShapeListener.onShapeChosen(holder.mShapeDrawable);
+                }
+            }
+        });
+        return holder;
+    }
+
+    @Override
+    public void onBindViewHolder(final VHolder holder, int position) {
+        holder.mShapeView.setBackground(mDrawables.get(position));
+        holder.mShapeDrawable = mDrawables.get(position);
+    }
+
+    @Override
+    public int getItemCount() {
+        return mDrawables.size();
+    }
+
+    public void setShapeListener(ShapeListener shapeListener) {
+        mShapeListener = shapeListener;
+    }
+}
+
+class VHolder extends RecyclerView.ViewHolder {
+    View mShapeView;
+    ShapeDrawable mShapeDrawable;
+
+    public VHolder(View itemView) {
+        super(itemView);
+        mShapeView = itemView.findViewById(R.id.shape);
+    }
+}
\ No newline at end of file
diff --git a/samples-flatfoot/ApiReviewDemo/app/src/main/res/layout/chosen_shape_layout.xml b/samples-flatfoot/ApiReviewDemo/app/src/main/res/layout/chosen_shape_layout.xml
new file mode 100644
index 0000000..2a88d65
--- /dev/null
+++ b/samples-flatfoot/ApiReviewDemo/app/src/main/res/layout/chosen_shape_layout.xml
@@ -0,0 +1,45 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  ~ Copyright (C) 2017 The Android Open Source Project
+  ~
+  ~ Licensed under the Apache License, Version 2.0 (the "License");
+  ~ you may not use this file except in compliance with the License.
+  ~ You may obtain a copy of the License at
+  ~
+  ~      http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing, software
+  ~ distributed under the License is distributed on an "AS IS" BASIS,
+  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  ~ See the License for the specific language governing permissions and
+  ~ limitations under the License.
+  -->
+
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+              android:layout_width="match_parent"
+              android:layout_height="wrap_content"
+              android:orientation="horizontal"
+              android:layout_marginBottom="20dp">
+
+    <TextView
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:layout_gravity="center_vertical"
+        android:text="@string/current_chosen_color"
+        android:textAppearance="@android:style/TextAppearance.DeviceDefault.Medium"
+        android:paddingRight="10dp"/>
+
+    <ImageView
+        android:id="@+id/color"
+        android:layout_width="40dp"
+        android:layout_height="40dp"
+        android:layout_gravity="center_vertical"/>
+
+    <TextView
+        android:id="@+id/none"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:layout_gravity="center_vertical"
+        android:textAppearance="@android:style/TextAppearance.DeviceDefault.Medium"
+        android:text="@string/none"/>
+</LinearLayout>
\ No newline at end of file
diff --git a/samples-flatfoot/ApiReviewDemo/app/src/main/res/layout/location_activity.xml b/samples-flatfoot/ApiReviewDemo/app/src/main/res/layout/location_activity.xml
new file mode 100644
index 0000000..6401cc2
--- /dev/null
+++ b/samples-flatfoot/ApiReviewDemo/app/src/main/res/layout/location_activity.xml
@@ -0,0 +1,26 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  ~ Copyright (C) 2017 The Android Open Source Project
+  ~
+  ~ Licensed under the Apache License, Version 2.0 (the "License");
+  ~ you may not use this file except in compliance with the License.
+  ~ You may obtain a copy of the License at
+  ~
+  ~      http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing, software
+  ~ distributed under the License is distributed on an "AS IS" BASIS,
+  ~ WITHOUT 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">
+    <TextView
+        android:id="@+id/location"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"/>
+</LinearLayout>
\ No newline at end of file
diff --git a/samples-flatfoot/ApiReviewDemo/app/src/main/res/layout/note_activity_layout.xml b/samples-flatfoot/ApiReviewDemo/app/src/main/res/layout/note_activity_layout.xml
new file mode 100644
index 0000000..5eb0fd8
--- /dev/null
+++ b/samples-flatfoot/ApiReviewDemo/app/src/main/res/layout/note_activity_layout.xml
@@ -0,0 +1,46 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  ~ Copyright (C) 2017 The Android Open Source Project
+  ~
+  ~ Licensed under the Apache License, Version 2.0 (the "License");
+  ~ you may not use this file except in compliance with the License.
+  ~ You may obtain a copy of the License at
+  ~
+  ~      http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing, software
+  ~ distributed under the License is distributed on an "AS IS" BASIS,
+  ~ WITHOUT 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">
+    <ProgressBar
+        android:id="@+id/progress_bar"
+        android:layout_width="match_parent"
+        android:layout_height="match_parent"
+        android:visibility="gone"/>
+    <LinearLayout
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        android:orientation="horizontal">
+        <TextView
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:text="@string/note_label"
+            android:layout_marginRight="3dp"/>
+
+        <TextView
+            android:id="@+id/note_label"
+            android:layout_width="match_parent"
+            android:layout_height="match_parent"/>
+    </LinearLayout>
+    <TextView
+        android:id="@+id/note_body"
+        android:layout_width="match_parent"
+        android:layout_height="match_parent"/>
+</LinearLayout>
\ No newline at end of file
diff --git a/samples-flatfoot/ApiReviewDemo/app/src/main/res/layout/shape_activity.xml b/samples-flatfoot/ApiReviewDemo/app/src/main/res/layout/shape_activity.xml
new file mode 100644
index 0000000..54548cf
--- /dev/null
+++ b/samples-flatfoot/ApiReviewDemo/app/src/main/res/layout/shape_activity.xml
@@ -0,0 +1,34 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  ~ Copyright (C) 2017 The Android Open Source Project
+  ~
+  ~ Licensed under the Apache License, Version 2.0 (the "License");
+  ~ you may not use this file except in compliance with the License.
+  ~ You may obtain a copy of the License at
+  ~
+  ~      http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing, software
+  ~ distributed under the License is distributed on an "AS IS" BASIS,
+  ~ WITHOUT 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">
+
+    <fragment
+        android:id="@+id/chosen_color_fragment"
+        android:name="com.android.flatfoot.apireviewdemo.lifecycle_04_shared_viewmodel.ChosenShapeFragment"
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"/>
+
+    <fragment
+        android:id="@+id/shape_chooser"
+        android:name="com.android.flatfoot.apireviewdemo.lifecycle_04_shared_viewmodel.ShapeChooserFragment"
+        android:layout_width="match_parent"
+        android:layout_height="match_parent"/>
+</LinearLayout>
\ No newline at end of file
diff --git a/samples-flatfoot/ApiReviewDemo/app/src/main/res/layout/shape_item.xml b/samples-flatfoot/ApiReviewDemo/app/src/main/res/layout/shape_item.xml
new file mode 100644
index 0000000..3f6221e
--- /dev/null
+++ b/samples-flatfoot/ApiReviewDemo/app/src/main/res/layout/shape_item.xml
@@ -0,0 +1,30 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  ~ Copyright (C) 2017 The Android Open Source Project
+  ~
+  ~ Licensed under the Apache License, Version 2.0 (the "License");
+  ~ you may not use this file except in compliance with the License.
+  ~ You may obtain a copy of the License at
+  ~
+  ~      http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing, software
+  ~ distributed under the License is distributed on an "AS IS" BASIS,
+  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  ~ See the License for the specific language governing permissions and
+  ~ limitations under the License.
+  -->
+
+<android.support.v7.widget.CardView xmlns:android="http://schemas.android.com/apk/res/android"
+                                    android:layout_width="wrap_content"
+                                    android:layout_height="wrap_content"
+                                    android:layout_margin="10dp"
+                                    android:layout_gravity="center"
+                                    android:background="@color/cardview_light_background">
+    <View android:id="@+id/shape"
+          android:layout_width="50dp"
+          android:layout_height="50dp"
+          android:layout_gravity="center"
+          android:layout_margin="10dp"
+    />
+</android.support.v7.widget.CardView>
\ No newline at end of file
diff --git a/samples-flatfoot/ApiReviewDemo/app/src/main/res/layout/switch_accounts.xml b/samples-flatfoot/ApiReviewDemo/app/src/main/res/layout/switch_accounts.xml
new file mode 100644
index 0000000..78bb969
--- /dev/null
+++ b/samples-flatfoot/ApiReviewDemo/app/src/main/res/layout/switch_accounts.xml
@@ -0,0 +1,31 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  ~ Copyright (C) 2017 The Android Open Source Project
+  ~
+  ~ Licensed under the Apache License, Version 2.0 (the "License");
+  ~ you may not use this file except in compliance with the License.
+  ~ You may obtain a copy of the License at
+  ~
+  ~      http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing, software
+  ~ distributed under the License is distributed on an "AS IS" BASIS,
+  ~ WITHOUT 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">
+    <Button android:id="@+id/switch_user" android:layout_width="wrap_content"
+            android:layout_height="wrap_content" android:text="@string/switch_account"/>
+    <ProgressBar android:id="@+id/loading_spinner" android:layout_width="match_parent"
+                 android:layout_height="wrap_content"/>
+    <TextView android:id="@+id/name" android:layout_width="wrap_content"
+              android:layout_height="wrap_content"/>
+    <TextView android:id="@+id/company" android:layout_width="wrap_content"
+              android:layout_height="wrap_content"/>
+    <Button android:id="@+id/force_refresh" android:layout_width="wrap_content"
+            android:layout_height="wrap_content" android:text="@string/force_refresh"/>>
+</LinearLayout>
\ No newline at end of file
diff --git a/samples-flatfoot/ApiReviewDemo/app/src/main/res/layout/view_model1.xml b/samples-flatfoot/ApiReviewDemo/app/src/main/res/layout/view_model1.xml
new file mode 100644
index 0000000..3d7f306
--- /dev/null
+++ b/samples-flatfoot/ApiReviewDemo/app/src/main/res/layout/view_model1.xml
@@ -0,0 +1,37 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  ~ Copyright (C) 2017 The Android Open Source Project
+  ~
+  ~ Licensed under the Apache License, Version 2.0 (the "License");
+  ~ you may not use this file except in compliance with the License.
+  ~ You may obtain a copy of the License at
+  ~
+  ~      http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing, software
+  ~ distributed under the License is distributed on an "AS IS" BASIS,
+  ~ WITHOUT 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">
+
+    <ProgressBar
+        android:id="@+id/loading_spinner"
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"/>
+
+    <TextView
+        android:id="@+id/name"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"/>
+
+    <TextView
+        android:id="@+id/company"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"/>
+</LinearLayout>
\ No newline at end of file
diff --git a/samples-flatfoot/ApiReviewDemo/app/src/main/res/mipmap-hdpi/ic_launcher.png b/samples-flatfoot/ApiReviewDemo/app/src/main/res/mipmap-hdpi/ic_launcher.png
new file mode 100644
index 0000000..cde69bc
--- /dev/null
+++ b/samples-flatfoot/ApiReviewDemo/app/src/main/res/mipmap-hdpi/ic_launcher.png
Binary files differ
diff --git a/samples-flatfoot/ApiReviewDemo/app/src/main/res/mipmap-mdpi/ic_launcher.png b/samples-flatfoot/ApiReviewDemo/app/src/main/res/mipmap-mdpi/ic_launcher.png
new file mode 100644
index 0000000..c133a0c
--- /dev/null
+++ b/samples-flatfoot/ApiReviewDemo/app/src/main/res/mipmap-mdpi/ic_launcher.png
Binary files differ
diff --git a/samples-flatfoot/ApiReviewDemo/app/src/main/res/mipmap-xhdpi/ic_launcher.png b/samples-flatfoot/ApiReviewDemo/app/src/main/res/mipmap-xhdpi/ic_launcher.png
new file mode 100644
index 0000000..bfa42f0
--- /dev/null
+++ b/samples-flatfoot/ApiReviewDemo/app/src/main/res/mipmap-xhdpi/ic_launcher.png
Binary files differ
diff --git a/samples-flatfoot/ApiReviewDemo/app/src/main/res/mipmap-xxhdpi/ic_launcher.png b/samples-flatfoot/ApiReviewDemo/app/src/main/res/mipmap-xxhdpi/ic_launcher.png
new file mode 100644
index 0000000..324e72c
--- /dev/null
+++ b/samples-flatfoot/ApiReviewDemo/app/src/main/res/mipmap-xxhdpi/ic_launcher.png
Binary files differ
diff --git a/samples-flatfoot/ApiReviewDemo/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png b/samples-flatfoot/ApiReviewDemo/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png
new file mode 100644
index 0000000..aee44e1
--- /dev/null
+++ b/samples-flatfoot/ApiReviewDemo/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png
Binary files differ
diff --git a/samples-flatfoot/ApiReviewDemo/app/src/main/res/values/colors.xml b/samples-flatfoot/ApiReviewDemo/app/src/main/res/values/colors.xml
new file mode 100644
index 0000000..86b4304
--- /dev/null
+++ b/samples-flatfoot/ApiReviewDemo/app/src/main/res/values/colors.xml
@@ -0,0 +1,22 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  ~ Copyright (C) 2017 The Android Open Source Project
+  ~
+  ~ Licensed under the Apache License, Version 2.0 (the "License");
+  ~ you may not use this file except in compliance with the License.
+  ~ You may obtain a copy of the License at
+  ~
+  ~      http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing, software
+  ~ distributed under the License is distributed on an "AS IS" BASIS,
+  ~ WITHOUT 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>
+    <color name="colorPrimary">#3F51B5</color>
+    <color name="colorPrimaryDark">#303F9F</color>
+    <color name="colorAccent">#FF4081</color>
+</resources>
diff --git a/samples-flatfoot/ApiReviewDemo/app/src/main/res/values/strings.xml b/samples-flatfoot/ApiReviewDemo/app/src/main/res/values/strings.xml
new file mode 100644
index 0000000..4482754
--- /dev/null
+++ b/samples-flatfoot/ApiReviewDemo/app/src/main/res/values/strings.xml
@@ -0,0 +1,28 @@
+<!--
+  ~ Copyright (C) 2017 The Android Open Source Project
+  ~
+  ~ Licensed under the Apache License, Version 2.0 (the "License");
+  ~ you may not use this file except in compliance with the License.
+  ~ You may obtain a copy of the License at
+  ~
+  ~      http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing, software
+  ~ distributed under the License is distributed on an "AS IS" BASIS,
+  ~ WITHOUT 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">ApiReviewDemo</string>
+    <string name="location_sample">Location Sample</string>
+    <string name="live_location_sample">LiveLocation Sample</string>
+    <string name="one_account_github">One Account</string>
+    <string name="force_refresh">Force refresh</string>
+    <string name="switch_account">Switch Account</string>
+    <string name="current_chosen_color">Current chosen color:</string>
+    <string name="none">None</string>
+    <string name="shapes_label">Shapes Activity</string>
+    <string name="note_label">Note label:</string>
+</resources>
diff --git a/samples-flatfoot/ApiReviewDemo/app/src/main/res/values/styles.xml b/samples-flatfoot/ApiReviewDemo/app/src/main/res/values/styles.xml
new file mode 100644
index 0000000..36ef789
--- /dev/null
+++ b/samples-flatfoot/ApiReviewDemo/app/src/main/res/values/styles.xml
@@ -0,0 +1,27 @@
+<!--
+  ~ Copyright (C) 2017 The Android Open Source Project
+  ~
+  ~ Licensed under the Apache License, Version 2.0 (the "License");
+  ~ you may not use this file except in compliance with the License.
+  ~ You may obtain a copy of the License at
+  ~
+  ~      http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing, software
+  ~ distributed under the License is distributed on an "AS IS" BASIS,
+  ~ WITHOUT 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. -->
+    <style name="AppTheme" parent="Theme.AppCompat.Light.DarkActionBar">
+        <!-- Customize your theme here. -->
+        <item name="colorPrimary">@color/colorPrimary</item>
+        <item name="colorPrimaryDark">@color/colorPrimaryDark</item>
+        <item name="colorAccent">@color/colorAccent</item>
+    </style>
+
+</resources>
diff --git a/samples-flatfoot/ApiReviewDemo/app/src/test/java/com/android/flatfoot/apireviewdemo/ExampleUnitTest.java b/samples-flatfoot/ApiReviewDemo/app/src/test/java/com/android/flatfoot/apireviewdemo/ExampleUnitTest.java
new file mode 100644
index 0000000..f3d51be
--- /dev/null
+++ b/samples-flatfoot/ApiReviewDemo/app/src/test/java/com/android/flatfoot/apireviewdemo/ExampleUnitTest.java
@@ -0,0 +1,33 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES 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.flatfoot.apireviewdemo;
+
+import static org.junit.Assert.assertEquals;
+
+import org.junit.Test;
+
+/**
+ * Example local unit test, which will execute on the development machine (host).
+ *
+ * @see <a href="http://d.android.com/tools/testing">Testing documentation</a>
+ */
+public class ExampleUnitTest {
+    @Test
+    public void addition_isCorrect() throws Exception {
+        assertEquals(4, 2 + 2);
+    }
+}
\ No newline at end of file
diff --git a/samples-flatfoot/ApiReviewDemo/build.gradle b/samples-flatfoot/ApiReviewDemo/build.gradle
new file mode 100644
index 0000000..a458edd
--- /dev/null
+++ b/samples-flatfoot/ApiReviewDemo/build.gradle
@@ -0,0 +1,43 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT 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 build file where you can add configuration options common to all sub-projects/modules.
+
+buildscript {
+    repositories {
+        jcenter()
+    }
+    dependencies {
+        classpath 'com.android.tools.build:gradle:2.3.0'
+
+        // NOTE: Do not place your application dependencies here; they belong
+        // in the individual module build.gradle files
+    }
+}
+
+allprojects { p ->
+    repositories {
+        jcenter()
+        def outFolder = new File(project.rootProject.rootDir, "../../../../out/host/gradle/frameworks/")
+        maven {
+            url "file://${new File(outFolder, "support/build/support_repo").absolutePath}"
+        }
+    }
+}
+
+task clean(type: Delete) {
+    delete rootProject.buildDir
+}
diff --git a/samples-flatfoot/ApiReviewDemo/gradle.properties b/samples-flatfoot/ApiReviewDemo/gradle.properties
new file mode 100644
index 0000000..be27115
--- /dev/null
+++ b/samples-flatfoot/ApiReviewDemo/gradle.properties
@@ -0,0 +1,33 @@
+#
+# Copyright (C) 2017 The Android Open Source Project
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#      http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+
+# Project-wide Gradle settings.
+
+# IDE (e.g. Android Studio) users:
+# Gradle settings configured through the IDE *will override*
+# any settings specified in this file.
+
+# For more details on how to configure your build environment visit
+# http://www.gradle.org/docs/current/userguide/build_environment.html
+
+# Specifies the JVM arguments used for the daemon process.
+# The setting is particularly useful for tweaking memory settings.
+org.gradle.jvmargs=-Xmx1536m
+
+# When configured, Gradle will run in incubating parallel mode.
+# This option should only be used with decoupled projects. More details, visit
+# http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects
+# org.gradle.parallel=true
diff --git a/samples-flatfoot/ApiReviewDemo/gradle/wrapper/gradle-wrapper.jar b/samples-flatfoot/ApiReviewDemo/gradle/wrapper/gradle-wrapper.jar
new file mode 100644
index 0000000..13372ae
--- /dev/null
+++ b/samples-flatfoot/ApiReviewDemo/gradle/wrapper/gradle-wrapper.jar
Binary files differ
diff --git a/samples-flatfoot/ApiReviewDemo/gradle/wrapper/gradle-wrapper.properties b/samples-flatfoot/ApiReviewDemo/gradle/wrapper/gradle-wrapper.properties
new file mode 100644
index 0000000..be2d403
--- /dev/null
+++ b/samples-flatfoot/ApiReviewDemo/gradle/wrapper/gradle-wrapper.properties
@@ -0,0 +1,22 @@
+#
+# Copyright (C) 2017 The Android Open Source Project
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#      http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+
+#Mon Dec 28 10:00:20 PST 2015
+distributionBase=GRADLE_USER_HOME
+distributionPath=wrapper/dists
+zipStoreBase=GRADLE_USER_HOME
+zipStorePath=wrapper/dists
+distributionUrl=https\://services.gradle.org/distributions/gradle-3.3-bin.zip
diff --git a/samples-flatfoot/ApiReviewDemo/gradlew b/samples-flatfoot/ApiReviewDemo/gradlew
new file mode 100755
index 0000000..9d82f78
--- /dev/null
+++ b/samples-flatfoot/ApiReviewDemo/gradlew
@@ -0,0 +1,160 @@
+#!/usr/bin/env bash
+
+##############################################################################
+##
+##  Gradle start up script for UN*X
+##
+##############################################################################
+
+# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
+DEFAULT_JVM_OPTS=""
+
+APP_NAME="Gradle"
+APP_BASE_NAME=`basename "$0"`
+
+# Use the maximum available, or set MAX_FD != -1 to use that value.
+MAX_FD="maximum"
+
+warn ( ) {
+    echo "$*"
+}
+
+die ( ) {
+    echo
+    echo "$*"
+    echo
+    exit 1
+}
+
+# OS specific support (must be 'true' or 'false').
+cygwin=false
+msys=false
+darwin=false
+case "`uname`" in
+  CYGWIN* )
+    cygwin=true
+    ;;
+  Darwin* )
+    darwin=true
+    ;;
+  MINGW* )
+    msys=true
+    ;;
+esac
+
+# Attempt to set APP_HOME
+# Resolve links: $0 may be a link
+PRG="$0"
+# Need this for relative symlinks.
+while [ -h "$PRG" ] ; do
+    ls=`ls -ld "$PRG"`
+    link=`expr "$ls" : '.*-> \(.*\)$'`
+    if expr "$link" : '/.*' > /dev/null; then
+        PRG="$link"
+    else
+        PRG=`dirname "$PRG"`"/$link"
+    fi
+done
+SAVED="`pwd`"
+cd "`dirname \"$PRG\"`/" >/dev/null
+APP_HOME="`pwd -P`"
+cd "$SAVED" >/dev/null
+
+CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
+
+# Determine the Java command to use to start the JVM.
+if [ -n "$JAVA_HOME" ] ; then
+    if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
+        # IBM's JDK on AIX uses strange locations for the executables
+        JAVACMD="$JAVA_HOME/jre/sh/java"
+    else
+        JAVACMD="$JAVA_HOME/bin/java"
+    fi
+    if [ ! -x "$JAVACMD" ] ; then
+        die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
+
+Please set the JAVA_HOME variable in your environment to match the
+location of your Java installation."
+    fi
+else
+    JAVACMD="java"
+    which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
+
+Please set the JAVA_HOME variable in your environment to match the
+location of your Java installation."
+fi
+
+# Increase the maximum file descriptors if we can.
+if [ "$cygwin" = "false" -a "$darwin" = "false" ] ; then
+    MAX_FD_LIMIT=`ulimit -H -n`
+    if [ $? -eq 0 ] ; then
+        if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then
+            MAX_FD="$MAX_FD_LIMIT"
+        fi
+        ulimit -n $MAX_FD
+        if [ $? -ne 0 ] ; then
+            warn "Could not set maximum file descriptor limit: $MAX_FD"
+        fi
+    else
+        warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT"
+    fi
+fi
+
+# For Darwin, add options to specify how the application appears in the dock
+if $darwin; then
+    GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\""
+fi
+
+# For Cygwin, switch paths to Windows format before running java
+if $cygwin ; then
+    APP_HOME=`cygpath --path --mixed "$APP_HOME"`
+    CLASSPATH=`cygpath --path --mixed "$CLASSPATH"`
+    JAVACMD=`cygpath --unix "$JAVACMD"`
+
+    # We build the pattern for arguments to be converted via cygpath
+    ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null`
+    SEP=""
+    for dir in $ROOTDIRSRAW ; do
+        ROOTDIRS="$ROOTDIRS$SEP$dir"
+        SEP="|"
+    done
+    OURCYGPATTERN="(^($ROOTDIRS))"
+    # Add a user-defined pattern to the cygpath arguments
+    if [ "$GRADLE_CYGPATTERN" != "" ] ; then
+        OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)"
+    fi
+    # Now convert the arguments - kludge to limit ourselves to /bin/sh
+    i=0
+    for arg in "$@" ; do
+        CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -`
+        CHECK2=`echo "$arg"|egrep -c "^-"`                                 ### Determine if an option
+
+        if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then                    ### Added a condition
+            eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"`
+        else
+            eval `echo args$i`="\"$arg\""
+        fi
+        i=$((i+1))
+    done
+    case $i in
+        (0) set -- ;;
+        (1) set -- "$args0" ;;
+        (2) set -- "$args0" "$args1" ;;
+        (3) set -- "$args0" "$args1" "$args2" ;;
+        (4) set -- "$args0" "$args1" "$args2" "$args3" ;;
+        (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;;
+        (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;;
+        (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;;
+        (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;;
+        (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;;
+    esac
+fi
+
+# Split up the JVM_OPTS And GRADLE_OPTS values into an array, following the shell quoting and substitution rules
+function splitJvmOpts() {
+    JVM_OPTS=("$@")
+}
+eval splitJvmOpts $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS
+JVM_OPTS[${#JVM_OPTS[*]}]="-Dorg.gradle.appname=$APP_BASE_NAME"
+
+exec "$JAVACMD" "${JVM_OPTS[@]}" -classpath "$CLASSPATH" org.gradle.wrapper.GradleWrapperMain "$@"
diff --git a/samples-flatfoot/ApiReviewDemo/gradlew.bat b/samples-flatfoot/ApiReviewDemo/gradlew.bat
new file mode 100644
index 0000000..aec9973
--- /dev/null
+++ b/samples-flatfoot/ApiReviewDemo/gradlew.bat
@@ -0,0 +1,90 @@
+@if "%DEBUG%" == "" @echo off

+@rem ##########################################################################

+@rem

+@rem  Gradle startup script for Windows

+@rem

+@rem ##########################################################################

+

+@rem Set local scope for the variables with windows NT shell

+if "%OS%"=="Windows_NT" setlocal

+

+@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.

+set DEFAULT_JVM_OPTS=

+

+set DIRNAME=%~dp0

+if "%DIRNAME%" == "" set DIRNAME=.

+set APP_BASE_NAME=%~n0

+set APP_HOME=%DIRNAME%

+

+@rem Find java.exe

+if defined JAVA_HOME goto findJavaFromJavaHome

+

+set JAVA_EXE=java.exe

+%JAVA_EXE% -version >NUL 2>&1

+if "%ERRORLEVEL%" == "0" goto init

+

+echo.

+echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.

+echo.

+echo Please set the JAVA_HOME variable in your environment to match the

+echo location of your Java installation.

+

+goto fail

+

+:findJavaFromJavaHome

+set JAVA_HOME=%JAVA_HOME:"=%

+set JAVA_EXE=%JAVA_HOME%/bin/java.exe

+

+if exist "%JAVA_EXE%" goto init

+

+echo.

+echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%

+echo.

+echo Please set the JAVA_HOME variable in your environment to match the

+echo location of your Java installation.

+

+goto fail

+

+:init

+@rem Get command-line arguments, handling Windowz variants

+

+if not "%OS%" == "Windows_NT" goto win9xME_args

+if "%@eval[2+2]" == "4" goto 4NT_args

+

+:win9xME_args

+@rem Slurp the command line arguments.

+set CMD_LINE_ARGS=

+set _SKIP=2

+

+:win9xME_args_slurp

+if "x%~1" == "x" goto execute

+

+set CMD_LINE_ARGS=%*

+goto execute

+

+:4NT_args

+@rem Get arguments from the 4NT Shell from JP Software

+set CMD_LINE_ARGS=%$

+

+:execute

+@rem Setup the command line

+

+set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar

+

+@rem Execute Gradle

+"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS%

+

+:end

+@rem End local scope for the variables with windows NT shell

+if "%ERRORLEVEL%"=="0" goto mainEnd

+

+:fail

+rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of

+rem the _cmd.exe /c_ return code!

+if  not "" == "%GRADLE_EXIT_CONSOLE%" exit 1

+exit /b 1

+

+:mainEnd

+if "%OS%"=="Windows_NT" endlocal

+

+:omega

diff --git a/samples-flatfoot/ApiReviewDemo/settings.gradle b/samples-flatfoot/ApiReviewDemo/settings.gradle
new file mode 100644
index 0000000..1df87d4
--- /dev/null
+++ b/samples-flatfoot/ApiReviewDemo/settings.gradle
@@ -0,0 +1,17 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+include ':app'
diff --git a/samples-flatfoot/ContentProviderSample/.gitignore b/samples-flatfoot/ContentProviderSample/.gitignore
new file mode 100644
index 0000000..39fb081
--- /dev/null
+++ b/samples-flatfoot/ContentProviderSample/.gitignore
@@ -0,0 +1,9 @@
+*.iml
+.gradle
+/local.properties
+/.idea/workspace.xml
+/.idea/libraries
+.DS_Store
+/build
+/captures
+.externalNativeBuild
diff --git a/samples-flatfoot/ContentProviderSample/app/.gitignore b/samples-flatfoot/ContentProviderSample/app/.gitignore
new file mode 100644
index 0000000..796b96d
--- /dev/null
+++ b/samples-flatfoot/ContentProviderSample/app/.gitignore
@@ -0,0 +1 @@
+/build
diff --git a/samples-flatfoot/ContentProviderSample/app/build.gradle b/samples-flatfoot/ContentProviderSample/app/build.gradle
new file mode 100644
index 0000000..72f77c8
--- /dev/null
+++ b/samples-flatfoot/ContentProviderSample/app/build.gradle
@@ -0,0 +1,64 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+apply plugin: 'com.android.application'
+
+android {
+    compileSdkVersion 25
+    buildToolsVersion "25.0.2"
+    defaultConfig {
+        applicationId "com.example.android.contentprovidersample"
+        minSdkVersion 14
+        targetSdkVersion 25
+        versionCode 1
+        versionName "1.0"
+        testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
+
+        // Write out the current schema of Room
+        javaCompileOptions {
+            annotationProcessorOptions {
+                arguments = ["room.schemaLocation": "$projectDir/schemas".toString()]
+            }
+        }
+    }
+    buildTypes {
+        release {
+            minifyEnabled false
+            proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
+        }
+    }
+}
+
+dependencies {
+    compile fileTree(dir: 'libs', include: ['*.jar'])
+
+    // Test
+    testCompile 'junit:junit:4.12'
+    androidTestCompile('com.android.support.test.espresso:espresso-core:2.2.2', {
+        exclude group: 'com.android.support', module: 'support-annotations'
+    })
+
+    // Support Libraries
+    compile 'com.android.support:appcompat-v7:25.3.1'
+    compile 'com.android.support:recyclerview-v7:25.3.1'
+
+    // App Toolkit
+    // TODO: Replace with the actual dependencies
+    compile "android.arch.lifecycle:extensions:1.0-SNAPSHOT"
+    compile "android.arch.persistence.room:runtime:1.0-SNAPSHOT"
+    annotationProcessor "android.arch.lifecycle:compiler:1.0-SNAPSHOT"
+    annotationProcessor "android.arch.persistence.room:compiler:1.0-SNAPSHOT"
+}
diff --git a/samples-flatfoot/ContentProviderSample/app/proguard-rules.pro b/samples-flatfoot/ContentProviderSample/app/proguard-rules.pro
new file mode 100644
index 0000000..06b266f
--- /dev/null
+++ b/samples-flatfoot/ContentProviderSample/app/proguard-rules.pro
@@ -0,0 +1,25 @@
+# Add project specific ProGuard rules here.
+# By default, the flags in this file are appended to flags specified
+# in /usr/local/google/home/yaraki/Android/Sdk/tools/proguard/proguard-android.txt
+# You can edit the include path and order by changing the proguardFiles
+# directive in build.gradle.
+#
+# For more details, see
+#   http://developer.android.com/guide/developing/tools/proguard.html
+
+# Add any project specific keep options here:
+
+# If your project uses WebView with JS, uncomment the following
+# and specify the fully qualified class name to the JavaScript interface
+# class:
+#-keepclassmembers class fqcn.of.javascript.interface.for.webview {
+#   public *;
+#}
+
+# Uncomment this to preserve the line number information for
+# debugging stack traces.
+#-keepattributes SourceFile,LineNumberTable
+
+# If you keep the line number information, uncomment this to
+# hide the original source file name.
+#-renamesourcefileattribute SourceFile
diff --git a/samples-flatfoot/ContentProviderSample/app/schemas/com.example.android.contentprovidersample.data.SampleDatabase/1.json b/samples-flatfoot/ContentProviderSample/app/schemas/com.example.android.contentprovidersample.data.SampleDatabase/1.json
new file mode 100644
index 0000000..d154d07
--- /dev/null
+++ b/samples-flatfoot/ContentProviderSample/app/schemas/com.example.android.contentprovidersample.data.SampleDatabase/1.json
@@ -0,0 +1,46 @@
+{
+  "formatVersion": 1,
+  "database": {
+    "version": 1,
+    "identityHash": "d612c3c0739239af787986e19d97626b",
+    "entities": [
+      {
+        "tableName": "cheeses",
+        "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`_id` INTEGER PRIMARY KEY AUTOINCREMENT, `name` TEXT)",
+        "fields": [
+          {
+            "fieldPath": "id",
+            "columnName": "_id",
+            "affinity": "INTEGER"
+          },
+          {
+            "fieldPath": "name",
+            "columnName": "name",
+            "affinity": "TEXT"
+          }
+        ],
+        "primaryKey": {
+          "columnNames": [
+            "_id"
+          ],
+          "autoGenerate": true
+        },
+        "indices": [
+          {
+            "name": "index_cheeses__id",
+            "unique": false,
+            "columnNames": [
+              "_id"
+            ],
+            "createSql": "CREATE  INDEX `index_cheeses__id` ON `${TABLE_NAME}` (`_id`)"
+          }
+        ],
+        "foreignKeys": []
+      }
+    ],
+    "setupQueries": [
+      "CREATE TABLE IF NOT EXISTS room_master_table (id INTEGER PRIMARY KEY,identity_hash TEXT)",
+      "INSERT OR REPLACE INTO room_master_table (id,identity_hash) VALUES(42, \"d612c3c0739239af787986e19d97626b\")"
+    ]
+  }
+}
\ No newline at end of file
diff --git a/samples-flatfoot/ContentProviderSample/app/src/androidTest/java/com/example/android/contentprovidersample/CheeseTest.java b/samples-flatfoot/ContentProviderSample/app/src/androidTest/java/com/example/android/contentprovidersample/CheeseTest.java
new file mode 100644
index 0000000..7ae4ee6
--- /dev/null
+++ b/samples-flatfoot/ContentProviderSample/app/src/androidTest/java/com/example/android/contentprovidersample/CheeseTest.java
@@ -0,0 +1,64 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES 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.contentprovidersample;
+
+import static org.hamcrest.Matchers.is;
+import static org.junit.Assert.assertThat;
+
+import android.arch.persistence.room.Room;
+import android.support.test.InstrumentationRegistry;
+import android.support.test.filters.SmallTest;
+import android.support.test.runner.AndroidJUnit4;
+
+import com.example.android.contentprovidersample.data.Cheese;
+import com.example.android.contentprovidersample.data.SampleDatabase;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.io.IOException;
+
+
+@RunWith(AndroidJUnit4.class)
+@SmallTest
+public class CheeseTest {
+
+    private SampleDatabase mDatabase;
+
+    @Before
+    public void createDatabase() {
+        mDatabase = Room.inMemoryDatabaseBuilder(InstrumentationRegistry.getTargetContext(),
+                SampleDatabase.class).build();
+    }
+
+    @After
+    public void closeDatabase() throws IOException {
+        mDatabase.close();
+    }
+
+    @Test
+    public void insertAndCount() {
+        assertThat(mDatabase.cheese().count(), is(0));
+        Cheese cheese = new Cheese();
+        cheese.name = "abc";
+        mDatabase.cheese().insert(cheese);
+        assertThat(mDatabase.cheese().count(), is(1));
+    }
+
+}
diff --git a/samples-flatfoot/ContentProviderSample/app/src/androidTest/java/com/example/android/contentprovidersample/SampleContentProviderTest.java b/samples-flatfoot/ContentProviderSample/app/src/androidTest/java/com/example/android/contentprovidersample/SampleContentProviderTest.java
new file mode 100644
index 0000000..860a551
--- /dev/null
+++ b/samples-flatfoot/ContentProviderSample/app/src/androidTest/java/com/example/android/contentprovidersample/SampleContentProviderTest.java
@@ -0,0 +1,162 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES 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.contentprovidersample;
+
+import static org.hamcrest.Matchers.is;
+import static org.hamcrest.Matchers.notNullValue;
+import static org.junit.Assert.assertThat;
+
+import android.content.ContentProviderOperation;
+import android.content.ContentProviderResult;
+import android.content.ContentResolver;
+import android.content.ContentValues;
+import android.content.Context;
+import android.content.OperationApplicationException;
+import android.database.Cursor;
+import android.net.Uri;
+import android.os.RemoteException;
+import android.support.test.InstrumentationRegistry;
+import android.support.test.filters.SmallTest;
+import android.support.test.runner.AndroidJUnit4;
+
+import com.example.android.contentprovidersample.data.Cheese;
+import com.example.android.contentprovidersample.data.SampleDatabase;
+import com.example.android.contentprovidersample.provider.SampleContentProvider;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.util.ArrayList;
+
+
+@RunWith(AndroidJUnit4.class)
+@SmallTest
+public class SampleContentProviderTest {
+
+    private ContentResolver mContentResolver;
+
+    @Before
+    public void setUp() {
+        final Context context = InstrumentationRegistry.getTargetContext();
+        SampleDatabase.switchToInMemory(context);
+        mContentResolver = context.getContentResolver();
+    }
+
+    @Test
+    public void cheese_initiallyEmpty() {
+        final Cursor cursor = mContentResolver.query(SampleContentProvider.URI_CHEESE,
+                new String[]{Cheese.COLUMN_NAME}, null, null, null);
+        assertThat(cursor, notNullValue());
+        assertThat(cursor.getCount(), is(0));
+        cursor.close();
+    }
+
+    @Test
+    public void cheese_insert() {
+        final Uri itemUri = mContentResolver.insert(SampleContentProvider.URI_CHEESE,
+                cheeseWithName("Daigo"));
+        assertThat(itemUri, notNullValue());
+        final Cursor cursor = mContentResolver.query(SampleContentProvider.URI_CHEESE,
+                new String[]{Cheese.COLUMN_NAME}, null, null, null);
+        assertThat(cursor, notNullValue());
+        assertThat(cursor.getCount(), is(1));
+        assertThat(cursor.moveToFirst(), is(true));
+        assertThat(cursor.getString(cursor.getColumnIndexOrThrow(Cheese.COLUMN_NAME)), is("Daigo"));
+        cursor.close();
+    }
+
+    @Test
+    public void cheese_update() {
+        final Uri itemUri = mContentResolver.insert(SampleContentProvider.URI_CHEESE,
+                cheeseWithName("Daigo"));
+        assertThat(itemUri, notNullValue());
+        final int count = mContentResolver.update(itemUri, cheeseWithName("Queso"), null, null);
+        assertThat(count, is(1));
+        final Cursor cursor = mContentResolver.query(SampleContentProvider.URI_CHEESE,
+                new String[]{Cheese.COLUMN_NAME}, null, null, null);
+        assertThat(cursor, notNullValue());
+        assertThat(cursor.getCount(), is(1));
+        assertThat(cursor.moveToFirst(), is(true));
+        assertThat(cursor.getString(cursor.getColumnIndexOrThrow(Cheese.COLUMN_NAME)), is("Queso"));
+        cursor.close();
+    }
+
+    @Test
+    public void cheese_delete() {
+        final Uri itemUri = mContentResolver.insert(SampleContentProvider.URI_CHEESE,
+                cheeseWithName("Daigo"));
+        assertThat(itemUri, notNullValue());
+        final Cursor cursor1 = mContentResolver.query(SampleContentProvider.URI_CHEESE,
+                new String[]{Cheese.COLUMN_NAME}, null, null, null);
+        assertThat(cursor1, notNullValue());
+        assertThat(cursor1.getCount(), is(1));
+        cursor1.close();
+        final int count = mContentResolver.delete(itemUri, null, null);
+        assertThat(count, is(1));
+        final Cursor cursor2 = mContentResolver.query(SampleContentProvider.URI_CHEESE,
+                new String[]{Cheese.COLUMN_NAME}, null, null, null);
+        assertThat(cursor2, notNullValue());
+        assertThat(cursor2.getCount(), is(0));
+        cursor2.close();
+    }
+
+    @Test
+    public void cheese_bulkInsert() {
+        final int count = mContentResolver.bulkInsert(SampleContentProvider.URI_CHEESE,
+                new ContentValues[]{
+                        cheeseWithName("Peynir"),
+                        cheeseWithName("Queso"),
+                        cheeseWithName("Daigo"),
+                });
+        assertThat(count, is(3));
+        final Cursor cursor = mContentResolver.query(SampleContentProvider.URI_CHEESE,
+                new String[]{Cheese.COLUMN_NAME}, null, null, null);
+        assertThat(cursor, notNullValue());
+        assertThat(cursor.getCount(), is(3));
+        cursor.close();
+    }
+
+    @Test
+    public void cheese_applyBatch() throws RemoteException, OperationApplicationException {
+        final ArrayList<ContentProviderOperation> operations = new ArrayList<>();
+        operations.add(ContentProviderOperation
+                .newInsert(SampleContentProvider.URI_CHEESE)
+                .withValue(Cheese.COLUMN_NAME, "Peynir")
+                .build());
+        operations.add(ContentProviderOperation
+                .newInsert(SampleContentProvider.URI_CHEESE)
+                .withValue(Cheese.COLUMN_NAME, "Queso")
+                .build());
+        final ContentProviderResult[] results = mContentResolver.applyBatch(
+                SampleContentProvider.AUTHORITY, operations);
+        assertThat(results.length, is(2));
+        final Cursor cursor = mContentResolver.query(SampleContentProvider.URI_CHEESE,
+                new String[]{Cheese.COLUMN_NAME}, null, null, null);
+        assertThat(cursor, notNullValue());
+        assertThat(cursor.getCount(), is(2));
+        assertThat(cursor.moveToFirst(), is(true));
+        cursor.close();
+    }
+
+    private ContentValues cheeseWithName(String name) {
+        final ContentValues values = new ContentValues();
+        values.put(Cheese.COLUMN_NAME, name);
+        return values;
+    }
+
+}
diff --git a/samples-flatfoot/ContentProviderSample/app/src/main/AndroidManifest.xml b/samples-flatfoot/ContentProviderSample/app/src/main/AndroidManifest.xml
new file mode 100644
index 0000000..82d25e4
--- /dev/null
+++ b/samples-flatfoot/ContentProviderSample/app/src/main/AndroidManifest.xml
@@ -0,0 +1,44 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+    Copyright (C) 2017 The Android Open Source Project
+    Licensed under the Apache License, Version 2.0 (the "License");
+    you may not use this file except in compliance with the License.
+    You may obtain a copy of the License at
+         http://www.apache.org/licenses/LICENSE-2.0
+    Unless required by applicable law or agreed to in writing, software
+    distributed under the License is distributed on an "AS IS" BASIS,
+    WITHOUT 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"
+    xmlns:tools="http://schemas.android.com/tools"
+    package="com.example.android.contentprovidersample">
+
+    <permission android:name="com.example.android.contentprovidersample.provider.READ_WRITE"/>
+
+    <application
+        android:allowBackup="false"
+        android:icon="@mipmap/ic_launcher"
+        android:label="@string/app_name"
+        android:supportsRtl="true"
+        android:theme="@style/Theme.ContentProviderSample"
+        tools:ignore="GoogleAppIndexingWarning">
+
+        <activity android:name=".MainActivity">
+            <intent-filter>
+                <action android:name="android.intent.action.MAIN"/>
+                <category android:name="android.intent.category.LAUNCHER"/>
+            </intent-filter>
+        </activity>
+
+        <provider
+            android:name=".provider.SampleContentProvider"
+            android:authorities="com.example.android.contentprovidersample.provider"
+            android:exported="true"
+            android:permission="com.example.android.contentprovidersample.provider.READ_WRITE"/>
+
+    </application>
+
+</manifest>
diff --git a/samples-flatfoot/ContentProviderSample/app/src/main/java/com/example/android/contentprovidersample/MainActivity.java b/samples-flatfoot/ContentProviderSample/app/src/main/java/com/example/android/contentprovidersample/MainActivity.java
new file mode 100644
index 0000000..89db65f
--- /dev/null
+++ b/samples-flatfoot/ContentProviderSample/app/src/main/java/com/example/android/contentprovidersample/MainActivity.java
@@ -0,0 +1,137 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES 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.contentprovidersample;
+
+import android.database.Cursor;
+import android.os.Bundle;
+import android.support.v4.app.LoaderManager;
+import android.support.v4.content.CursorLoader;
+import android.support.v4.content.Loader;
+import android.support.v7.app.AppCompatActivity;
+import android.support.v7.widget.LinearLayoutManager;
+import android.support.v7.widget.RecyclerView;
+import android.view.LayoutInflater;
+import android.view.ViewGroup;
+import android.widget.TextView;
+
+import com.example.android.contentprovidersample.data.Cheese;
+import com.example.android.contentprovidersample.provider.SampleContentProvider;
+
+
+/**
+ * Not very relevant to Room. This just shows data from {@link SampleContentProvider}.
+ *
+ * <p>Since the data is exposed through the ContentProvider, other apps can read and write the
+ * content in a similar manner to this.</p>
+ */
+public class MainActivity extends AppCompatActivity {
+
+    private static final int LOADER_CHEESES = 1;
+
+    private CheeseAdapter mCheeseAdapter;
+
+    @Override
+    protected void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+        setContentView(R.layout.main_activity);
+
+        final RecyclerView list = (RecyclerView) findViewById(R.id.list);
+        list.setLayoutManager(new LinearLayoutManager(list.getContext()));
+        mCheeseAdapter = new CheeseAdapter();
+        list.setAdapter(mCheeseAdapter);
+
+        getSupportLoaderManager().initLoader(LOADER_CHEESES, null, mLoaderCallbacks);
+    }
+
+    private LoaderManager.LoaderCallbacks<Cursor> mLoaderCallbacks =
+            new LoaderManager.LoaderCallbacks<Cursor>() {
+
+                @Override
+                public Loader<Cursor> onCreateLoader(int id, Bundle args) {
+                    switch (id) {
+                        case LOADER_CHEESES:
+                            return new CursorLoader(getApplicationContext(),
+                                    SampleContentProvider.URI_CHEESE,
+                                    new String[]{Cheese.COLUMN_NAME},
+                                    null, null, null);
+                        default:
+                            throw new IllegalArgumentException();
+                    }
+                }
+
+                @Override
+                public void onLoadFinished(Loader<Cursor> loader, Cursor data) {
+                    switch (loader.getId()) {
+                        case LOADER_CHEESES:
+                            mCheeseAdapter.setCheeses(data);
+                            break;
+                    }
+                }
+
+                @Override
+                public void onLoaderReset(Loader<Cursor> loader) {
+                    switch (loader.getId()) {
+                        case LOADER_CHEESES:
+                            mCheeseAdapter.setCheeses(null);
+                            break;
+                    }
+                }
+
+            };
+
+    private static class CheeseAdapter extends RecyclerView.Adapter<CheeseAdapter.ViewHolder> {
+
+        private Cursor mCursor;
+
+        @Override
+        public ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
+            return new ViewHolder(parent);
+        }
+
+        @Override
+        public void onBindViewHolder(ViewHolder holder, int position) {
+            if (mCursor.moveToPosition(position)) {
+                holder.mText.setText(mCursor.getString(
+                        mCursor.getColumnIndexOrThrow(Cheese.COLUMN_NAME)));
+            }
+        }
+
+        @Override
+        public int getItemCount() {
+            return mCursor == null ? 0 : mCursor.getCount();
+        }
+
+        void setCheeses(Cursor cursor) {
+            mCursor = cursor;
+            notifyDataSetChanged();
+        }
+
+        static class ViewHolder extends RecyclerView.ViewHolder {
+
+            TextView mText;
+
+            ViewHolder(ViewGroup parent) {
+                super(LayoutInflater.from(parent.getContext()).inflate(
+                        android.R.layout.simple_list_item_1, parent, false));
+                mText = (TextView) itemView.findViewById(android.R.id.text1);
+            }
+
+        }
+
+    }
+
+}
diff --git a/samples-flatfoot/ContentProviderSample/app/src/main/java/com/example/android/contentprovidersample/data/Cheese.java b/samples-flatfoot/ContentProviderSample/app/src/main/java/com/example/android/contentprovidersample/data/Cheese.java
new file mode 100644
index 0000000..4534e13
--- /dev/null
+++ b/samples-flatfoot/ContentProviderSample/app/src/main/java/com/example/android/contentprovidersample/data/Cheese.java
@@ -0,0 +1,201 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES 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.contentprovidersample.data;
+
+import android.arch.persistence.room.ColumnInfo;
+import android.arch.persistence.room.Entity;
+import android.arch.persistence.room.PrimaryKey;
+import android.content.ContentValues;
+import android.provider.BaseColumns;
+
+
+/**
+ * Represents one record of the Cheese table.
+ */
+@Entity(tableName = Cheese.TABLE_NAME)
+public class Cheese {
+
+    /** The name of the Cheese table. */
+    public static final String TABLE_NAME = "cheeses";
+
+    /** The name of the ID column. */
+    public static final String COLUMN_ID = BaseColumns._ID;
+
+    /** The name of the name column. */
+    public static final String COLUMN_NAME = "name";
+
+    /** The unique ID of the cheese. */
+    @PrimaryKey(autoGenerate = true)
+    @ColumnInfo(index = true, name = COLUMN_ID)
+    public long id;
+
+    /** The name of the cheese. */
+    @ColumnInfo(name = COLUMN_NAME)
+    public String name;
+
+    /**
+     * Create a new {@link Cheese} from the specified {@link ContentValues}.
+     *
+     * @param values A {@link ContentValues} that at least contain {@link #COLUMN_NAME}.
+     * @return A newly created {@link Cheese} instance.
+     */
+    public static Cheese fromContentValues(ContentValues values) {
+        final Cheese cheese = new Cheese();
+        if (values.containsKey(COLUMN_ID)) {
+            cheese.id = values.getAsLong(COLUMN_ID);
+        }
+        if (values.containsKey(COLUMN_NAME)) {
+            cheese.name = values.getAsString(COLUMN_NAME);
+        }
+        return cheese;
+    }
+
+    /** Dummy data. */
+    static final String[] CHEESES = {
+            "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-flatfoot/ContentProviderSample/app/src/main/java/com/example/android/contentprovidersample/data/CheeseDao.java b/samples-flatfoot/ContentProviderSample/app/src/main/java/com/example/android/contentprovidersample/data/CheeseDao.java
new file mode 100644
index 0000000..ea007c5
--- /dev/null
+++ b/samples-flatfoot/ContentProviderSample/app/src/main/java/com/example/android/contentprovidersample/data/CheeseDao.java
@@ -0,0 +1,93 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES 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.contentprovidersample.data;
+
+import android.arch.persistence.room.Dao;
+import android.arch.persistence.room.Insert;
+import android.arch.persistence.room.Query;
+import android.arch.persistence.room.Update;
+import android.database.Cursor;
+
+
+/**
+ * Data access object for Cheese.
+ */
+@Dao
+public interface CheeseDao {
+
+    /**
+     * Counts the number of cheeses in the table.
+     *
+     * @return The number of cheeses.
+     */
+    @Query("SELECT COUNT(*) FROM " + Cheese.TABLE_NAME)
+    int count();
+
+    /**
+     * Inserts a cheese into the table.
+     *
+     * @param cheese A new cheese.
+     * @return The row ID of the newly inserted cheese.
+     */
+    @Insert
+    long insert(Cheese cheese);
+
+    /**
+     * Inserts multiple cheeses into the database
+     *
+     * @param cheeses An array of new cheeses.
+     * @return The row IDs of the newly inserted cheeses.
+     */
+    @Insert
+    long[] insertAll(Cheese[] cheeses);
+
+    /**
+     * Select all cheeses.
+     *
+     * @return A {@link Cursor} of all the cheeses in the table.
+     */
+    @Query("SELECT * FROM " + Cheese.TABLE_NAME)
+    Cursor selectAll();
+
+    /**
+     * Select a cheese by the ID.
+     *
+     * @param id The row ID.
+     * @return A {@link Cursor} of the selected cheese.
+     */
+    @Query("SELECT * FROM " + Cheese.TABLE_NAME + " WHERE " + Cheese.COLUMN_ID + " = :id")
+    Cursor selectById(long id);
+
+    /**
+     * Delete a cheese by the ID.
+     *
+     * @param id The row ID.
+     * @return A number of cheeses deleted. This should always be {@code 1}.
+     */
+    @Query("DELETE FROM " + Cheese.TABLE_NAME + " WHERE " + Cheese.COLUMN_ID + " = :id")
+    int deleteById(long id);
+
+    /**
+     * Update the cheese. The cheese is identified by the row ID.
+     *
+     * @param cheese The cheese to update.
+     * @return A number of cheeses updated. This should always be {@code 1}.
+     */
+    @Update
+    int update(Cheese cheese);
+
+}
diff --git a/samples-flatfoot/ContentProviderSample/app/src/main/java/com/example/android/contentprovidersample/data/SampleDatabase.java b/samples-flatfoot/ContentProviderSample/app/src/main/java/com/example/android/contentprovidersample/data/SampleDatabase.java
new file mode 100644
index 0000000..9f27bf0
--- /dev/null
+++ b/samples-flatfoot/ContentProviderSample/app/src/main/java/com/example/android/contentprovidersample/data/SampleDatabase.java
@@ -0,0 +1,87 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES 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.contentprovidersample.data;
+
+import android.arch.persistence.room.Database;
+import android.arch.persistence.room.Room;
+import android.arch.persistence.room.RoomDatabase;
+import android.content.Context;
+import android.support.annotation.VisibleForTesting;
+
+
+/**
+ * The Room database.
+ */
+@Database(entities = {Cheese.class}, version = 1)
+public abstract class SampleDatabase extends RoomDatabase {
+
+    /**
+     * @return The DAO for the Cheese table.
+     */
+    @SuppressWarnings("WeakerAccess")
+    public abstract CheeseDao cheese();
+
+    /** The only instance */
+    private static SampleDatabase sInstance;
+
+    /**
+     * Gets the singleton instance of SampleDatabase.
+     *
+     * @param context The context.
+     * @return The singleton instance of SampleDatabase.
+     */
+    public static synchronized SampleDatabase getInstance(Context context) {
+        if (sInstance == null) {
+            sInstance = Room
+                    .databaseBuilder(context.getApplicationContext(), SampleDatabase.class, "ex")
+                    .build();
+            sInstance.populateInitialData();
+        }
+        return sInstance;
+    }
+
+    /**
+     * Switches the internal implementation with an empty in-memory database.
+     *
+     * @param context The context.
+     */
+    @VisibleForTesting
+    public static void switchToInMemory(Context context) {
+        sInstance = Room.inMemoryDatabaseBuilder(context.getApplicationContext(),
+                SampleDatabase.class).build();
+    }
+
+    /**
+     * Inserts the dummy data into the database if it is currently empty.
+     */
+    private void populateInitialData() {
+        if (cheese().count() == 0) {
+            Cheese cheese = new Cheese();
+            beginTransaction();
+            try {
+                for (int i = 0; i < Cheese.CHEESES.length; i++) {
+                    cheese.name = Cheese.CHEESES[i];
+                    cheese().insert(cheese);
+                }
+                setTransactionSuccessful();
+            } finally {
+                endTransaction();
+            }
+        }
+    }
+
+}
diff --git a/samples-flatfoot/ContentProviderSample/app/src/main/java/com/example/android/contentprovidersample/provider/SampleContentProvider.java b/samples-flatfoot/ContentProviderSample/app/src/main/java/com/example/android/contentprovidersample/provider/SampleContentProvider.java
new file mode 100644
index 0000000..00ae751
--- /dev/null
+++ b/samples-flatfoot/ContentProviderSample/app/src/main/java/com/example/android/contentprovidersample/provider/SampleContentProvider.java
@@ -0,0 +1,213 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES 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.contentprovidersample.provider;
+
+import android.content.ContentProvider;
+import android.content.ContentProviderOperation;
+import android.content.ContentProviderResult;
+import android.content.ContentUris;
+import android.content.ContentValues;
+import android.content.Context;
+import android.content.OperationApplicationException;
+import android.content.UriMatcher;
+import android.database.Cursor;
+import android.net.Uri;
+import android.support.annotation.NonNull;
+import android.support.annotation.Nullable;
+
+import com.example.android.contentprovidersample.data.Cheese;
+import com.example.android.contentprovidersample.data.CheeseDao;
+import com.example.android.contentprovidersample.data.SampleDatabase;
+
+import java.util.ArrayList;
+
+
+/**
+ * A {@link ContentProvider} based on a Room database.
+ *
+ * <p>Note that you don't need to implement a ContentProvider unless you want to expose the data
+ * outside your process or your application already uses a ContentProvider.</p>
+ */
+public class SampleContentProvider extends ContentProvider {
+
+    /** The authority of this content provider. */
+    public static final String AUTHORITY = "com.example.android.contentprovidersample.provider";
+
+    /** The URI for the Cheese table. */
+    public static final Uri URI_CHEESE = Uri.parse(
+            "content://" + AUTHORITY + "/" + Cheese.TABLE_NAME);
+
+    /** The match code for some items in the Cheese table. */
+    private static final int CODE_CHEESE_DIR = 1;
+
+    /** The match code for an item in the Cheese table. */
+    private static final int CODE_CHEESE_ITEM = 2;
+
+    /** The URI matcher. */
+    private static final UriMatcher MATCHER = new UriMatcher(UriMatcher.NO_MATCH);
+
+    static {
+        MATCHER.addURI(AUTHORITY, Cheese.TABLE_NAME, CODE_CHEESE_DIR);
+        MATCHER.addURI(AUTHORITY, Cheese.TABLE_NAME + "/*", CODE_CHEESE_ITEM);
+    }
+
+    @Override
+    public boolean onCreate() {
+        return true;
+    }
+
+    @Nullable
+    @Override
+    public Cursor query(@NonNull Uri uri, @Nullable String[] projection, @Nullable String selection,
+            @Nullable String[] selectionArgs, @Nullable String sortOrder) {
+        final int code = MATCHER.match(uri);
+        if (code == CODE_CHEESE_DIR || code == CODE_CHEESE_ITEM) {
+            final Context context = getContext();
+            if (context == null) {
+                return null;
+            }
+            CheeseDao cheese = SampleDatabase.getInstance(context).cheese();
+            final Cursor cursor;
+            if (code == CODE_CHEESE_DIR) {
+                cursor = cheese.selectAll();
+            } else {
+                cursor = cheese.selectById(ContentUris.parseId(uri));
+            }
+            cursor.setNotificationUri(context.getContentResolver(), uri);
+            return cursor;
+        } else {
+            throw new IllegalArgumentException("Unknown URI: " + uri);
+        }
+    }
+
+    @Nullable
+    @Override
+    public String getType(@NonNull Uri uri) {
+        switch (MATCHER.match(uri)) {
+            case CODE_CHEESE_DIR:
+                return "vnd.android.cursor.dir/" + AUTHORITY + "." + Cheese.TABLE_NAME;
+            case CODE_CHEESE_ITEM:
+                return "vnd.android.cursor.item/" + AUTHORITY + "." + Cheese.TABLE_NAME;
+            default:
+                throw new IllegalArgumentException("Unknown URI: " + uri);
+        }
+    }
+
+    @Nullable
+    @Override
+    public Uri insert(@NonNull Uri uri, @Nullable ContentValues values) {
+        switch (MATCHER.match(uri)) {
+            case CODE_CHEESE_DIR:
+                final Context context = getContext();
+                if (context == null) {
+                    return null;
+                }
+                final long id = SampleDatabase.getInstance(context).cheese()
+                        .insert(Cheese.fromContentValues(values));
+                context.getContentResolver().notifyChange(uri, null);
+                return ContentUris.withAppendedId(uri, id);
+            case CODE_CHEESE_ITEM:
+                throw new IllegalArgumentException("Invalid URI, cannot insert with ID: " + uri);
+            default:
+                throw new IllegalArgumentException("Unknown URI: " + uri);
+        }
+    }
+
+    @Override
+    public int delete(@NonNull Uri uri, @Nullable String selection,
+            @Nullable String[] selectionArgs) {
+        switch (MATCHER.match(uri)) {
+            case CODE_CHEESE_DIR:
+                throw new IllegalArgumentException("Invalid URI, cannot update without ID" + uri);
+            case CODE_CHEESE_ITEM:
+                final Context context = getContext();
+                if (context == null) {
+                    return 0;
+                }
+                final int count = SampleDatabase.getInstance(context).cheese()
+                        .deleteById(ContentUris.parseId(uri));
+                context.getContentResolver().notifyChange(uri, null);
+                return count;
+            default:
+                throw new IllegalArgumentException("Unknown URI: " + uri);
+        }
+    }
+
+    @Override
+    public int update(@NonNull Uri uri, @Nullable ContentValues values, @Nullable String selection,
+            @Nullable String[] selectionArgs) {
+        switch (MATCHER.match(uri)) {
+            case CODE_CHEESE_DIR:
+                throw new IllegalArgumentException("Invalid URI, cannot update without ID" + uri);
+            case CODE_CHEESE_ITEM:
+                final Context context = getContext();
+                if (context == null) {
+                    return 0;
+                }
+                final Cheese cheese = Cheese.fromContentValues(values);
+                cheese.id = ContentUris.parseId(uri);
+                final int count = SampleDatabase.getInstance(context).cheese()
+                        .update(cheese);
+                context.getContentResolver().notifyChange(uri, null);
+                return count;
+            default:
+                throw new IllegalArgumentException("Unknown URI: " + uri);
+        }
+    }
+
+    @NonNull
+    @Override
+    public ContentProviderResult[] applyBatch(
+            @NonNull ArrayList<ContentProviderOperation> operations)
+            throws OperationApplicationException {
+        final Context context = getContext();
+        if (context == null) {
+            return new ContentProviderResult[0];
+        }
+        final SampleDatabase database = SampleDatabase.getInstance(context);
+        database.beginTransaction();
+        try {
+            final ContentProviderResult[] result = super.applyBatch(operations);
+            database.setTransactionSuccessful();
+            return result;
+        } finally {
+            database.endTransaction();
+        }
+    }
+
+    @Override
+    public int bulkInsert(@NonNull Uri uri, @NonNull ContentValues[] valuesArray) {
+        switch (MATCHER.match(uri)) {
+            case CODE_CHEESE_DIR:
+                final Context context = getContext();
+                if (context == null) {
+                    return 0;
+                }
+                final SampleDatabase database = SampleDatabase.getInstance(context);
+                final Cheese[] cheeses = new Cheese[valuesArray.length];
+                for (int i = 0; i < valuesArray.length; i++) {
+                    cheeses[i] = Cheese.fromContentValues(valuesArray[i]);
+                }
+                return database.cheese().insertAll(cheeses).length;
+            case CODE_CHEESE_ITEM:
+                throw new IllegalArgumentException("Invalid URI, cannot insert with ID: " + uri);
+            default:
+                throw new IllegalArgumentException("Unknown URI: " + uri);
+        }
+    }
+
+}
diff --git a/samples-flatfoot/ContentProviderSample/app/src/main/res/layout/main_activity.xml b/samples-flatfoot/ContentProviderSample/app/src/main/res/layout/main_activity.xml
new file mode 100644
index 0000000..24592dd
--- /dev/null
+++ b/samples-flatfoot/ContentProviderSample/app/src/main/res/layout/main_activity.xml
@@ -0,0 +1,24 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+    Copyright (C) 2017 The Android Open Source Project
+    Licensed under the Apache License, Version 2.0 (the "License");
+    you may not use this file except in compliance with the License.
+    You may obtain a copy of the License at
+         http://www.apache.org/licenses/LICENSE-2.0
+    Unless required by applicable law or agreed to in writing, software
+    distributed under the License is distributed on an "AS IS" BASIS,
+    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+    See the License for the specific language governing permissions and
+    limitations under the License.
+-->
+<android.support.v7.widget.RecyclerView
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:tools="http://schemas.android.com/tools"
+    android:id="@+id/list"
+    android:layout_width="match_parent"
+    android:layout_height="match_parent"
+    android:clipToPadding="false"
+    android:paddingBottom="8dp"
+    android:paddingTop="8dp"
+    android:scrollbars="vertical"
+    tools:context="com.example.android.contentprovidersample.MainActivity"/>
diff --git a/samples-flatfoot/ContentProviderSample/app/src/main/res/mipmap-hdpi/ic_launcher.png b/samples-flatfoot/ContentProviderSample/app/src/main/res/mipmap-hdpi/ic_launcher.png
new file mode 100644
index 0000000..821d510
--- /dev/null
+++ b/samples-flatfoot/ContentProviderSample/app/src/main/res/mipmap-hdpi/ic_launcher.png
Binary files differ
diff --git a/samples-flatfoot/ContentProviderSample/app/src/main/res/mipmap-mdpi/ic_launcher.png b/samples-flatfoot/ContentProviderSample/app/src/main/res/mipmap-mdpi/ic_launcher.png
new file mode 100644
index 0000000..cbe549e
--- /dev/null
+++ b/samples-flatfoot/ContentProviderSample/app/src/main/res/mipmap-mdpi/ic_launcher.png
Binary files differ
diff --git a/samples-flatfoot/ContentProviderSample/app/src/main/res/mipmap-xhdpi/ic_launcher.png b/samples-flatfoot/ContentProviderSample/app/src/main/res/mipmap-xhdpi/ic_launcher.png
new file mode 100644
index 0000000..ca02616
--- /dev/null
+++ b/samples-flatfoot/ContentProviderSample/app/src/main/res/mipmap-xhdpi/ic_launcher.png
Binary files differ
diff --git a/samples-flatfoot/ContentProviderSample/app/src/main/res/mipmap-xxhdpi/ic_launcher.png b/samples-flatfoot/ContentProviderSample/app/src/main/res/mipmap-xxhdpi/ic_launcher.png
new file mode 100644
index 0000000..1a7720c
--- /dev/null
+++ b/samples-flatfoot/ContentProviderSample/app/src/main/res/mipmap-xxhdpi/ic_launcher.png
Binary files differ
diff --git a/samples-flatfoot/ContentProviderSample/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png b/samples-flatfoot/ContentProviderSample/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png
new file mode 100644
index 0000000..e12fb7a
--- /dev/null
+++ b/samples-flatfoot/ContentProviderSample/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png
Binary files differ
diff --git a/samples-flatfoot/ContentProviderSample/app/src/main/res/values/colors.xml b/samples-flatfoot/ContentProviderSample/app/src/main/res/values/colors.xml
new file mode 100644
index 0000000..e1723942
--- /dev/null
+++ b/samples-flatfoot/ContentProviderSample/app/src/main/res/values/colors.xml
@@ -0,0 +1,18 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+    Copyright (C) 2017 The Android Open Source Project
+    Licensed under the Apache License, Version 2.0 (the "License");
+    you may not use this file except in compliance with the License.
+    You may obtain a copy of the License at
+         http://www.apache.org/licenses/LICENSE-2.0
+    Unless required by applicable law or agreed to in writing, software
+    distributed under the License is distributed on an "AS IS" BASIS,
+    WITHOUT 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>
+    <color name="primary">#009688</color>
+    <color name="primary_dark">#00796B</color>
+    <color name="accent">#536DFE</color>
+</resources>
diff --git a/samples-flatfoot/ContentProviderSample/app/src/main/res/values/strings.xml b/samples-flatfoot/ContentProviderSample/app/src/main/res/values/strings.xml
new file mode 100644
index 0000000..b2e4230
--- /dev/null
+++ b/samples-flatfoot/ContentProviderSample/app/src/main/res/values/strings.xml
@@ -0,0 +1,16 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+    Copyright (C) 2017 The Android Open Source Project
+    Licensed under the Apache License, Version 2.0 (the "License");
+    you may not use this file except in compliance with the License.
+    You may obtain a copy of the License at
+         http://www.apache.org/licenses/LICENSE-2.0
+    Unless required by applicable law or agreed to in writing, software
+    distributed under the License is distributed on an "AS IS" BASIS,
+    WITHOUT 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">ContentProvider Sample</string>
+</resources>
diff --git a/samples-flatfoot/ContentProviderSample/app/src/main/res/values/styles.xml b/samples-flatfoot/ContentProviderSample/app/src/main/res/values/styles.xml
new file mode 100644
index 0000000..4a858f5
--- /dev/null
+++ b/samples-flatfoot/ContentProviderSample/app/src/main/res/values/styles.xml
@@ -0,0 +1,22 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+    Copyright (C) 2017 The Android Open Source Project
+    Licensed under the Apache License, Version 2.0 (the "License");
+    you may not use this file except in compliance with the License.
+    You may obtain a copy of the License at
+         http://www.apache.org/licenses/LICENSE-2.0
+    Unless required by applicable law or agreed to in writing, software
+    distributed under the License is distributed on an "AS IS" BASIS,
+    WITHOUT 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="Theme.ContentProviderSample" parent="Theme.AppCompat.Light.DarkActionBar">
+        <item name="colorPrimary">@color/primary</item>
+        <item name="colorPrimaryDark">@color/primary_dark</item>
+        <item name="colorAccent">@color/accent</item>
+    </style>
+
+</resources>
diff --git a/samples-flatfoot/ContentProviderSample/build.gradle b/samples-flatfoot/ContentProviderSample/build.gradle
new file mode 100644
index 0000000..79c0183
--- /dev/null
+++ b/samples-flatfoot/ContentProviderSample/build.gradle
@@ -0,0 +1,41 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+buildscript {
+    repositories {
+        jcenter()
+    }
+    dependencies {
+        classpath 'com.android.tools.build:gradle:2.4.0-alpha6'
+
+        // NOTE: Do not place your application dependencies here; they belong
+        // in the individual module build.gradle files
+    }
+}
+
+allprojects {
+    repositories {
+        jcenter()
+        // TODO: Replace this
+        maven {
+            url "file:///usr/local/google_ssd/android/m2repository"
+        }
+    }
+}
+
+task clean(type: Delete) {
+    delete rootProject.buildDir
+}
diff --git a/samples-flatfoot/ContentProviderSample/gradle.properties b/samples-flatfoot/ContentProviderSample/gradle.properties
new file mode 100644
index 0000000..aac7c9b
--- /dev/null
+++ b/samples-flatfoot/ContentProviderSample/gradle.properties
@@ -0,0 +1,17 @@
+# Project-wide Gradle settings.
+
+# IDE (e.g. Android Studio) users:
+# Gradle settings configured through the IDE *will override*
+# any settings specified in this file.
+
+# For more details on how to configure your build environment visit
+# http://www.gradle.org/docs/current/userguide/build_environment.html
+
+# Specifies the JVM arguments used for the daemon process.
+# The setting is particularly useful for tweaking memory settings.
+org.gradle.jvmargs=-Xmx1536m
+
+# When configured, Gradle will run in incubating parallel mode.
+# This option should only be used with decoupled projects. More details, visit
+# http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects
+# org.gradle.parallel=true
diff --git a/samples-flatfoot/ContentProviderSample/gradle/wrapper/gradle-wrapper.jar b/samples-flatfoot/ContentProviderSample/gradle/wrapper/gradle-wrapper.jar
new file mode 100644
index 0000000..13372ae
--- /dev/null
+++ b/samples-flatfoot/ContentProviderSample/gradle/wrapper/gradle-wrapper.jar
Binary files differ
diff --git a/samples-flatfoot/ContentProviderSample/gradle/wrapper/gradle-wrapper.properties b/samples-flatfoot/ContentProviderSample/gradle/wrapper/gradle-wrapper.properties
new file mode 100644
index 0000000..c6e5060
--- /dev/null
+++ b/samples-flatfoot/ContentProviderSample/gradle/wrapper/gradle-wrapper.properties
@@ -0,0 +1,6 @@
+#Fri Apr 21 15:24:08 JST 2017
+distributionBase=GRADLE_USER_HOME
+distributionPath=wrapper/dists
+zipStoreBase=GRADLE_USER_HOME
+zipStorePath=wrapper/dists
+distributionUrl=https\://services.gradle.org/distributions/gradle-3.4.1-all.zip
diff --git a/samples-flatfoot/ContentProviderSample/gradlew b/samples-flatfoot/ContentProviderSample/gradlew
new file mode 100755
index 0000000..9d82f78
--- /dev/null
+++ b/samples-flatfoot/ContentProviderSample/gradlew
@@ -0,0 +1,160 @@
+#!/usr/bin/env bash
+
+##############################################################################
+##
+##  Gradle start up script for UN*X
+##
+##############################################################################
+
+# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
+DEFAULT_JVM_OPTS=""
+
+APP_NAME="Gradle"
+APP_BASE_NAME=`basename "$0"`
+
+# Use the maximum available, or set MAX_FD != -1 to use that value.
+MAX_FD="maximum"
+
+warn ( ) {
+    echo "$*"
+}
+
+die ( ) {
+    echo
+    echo "$*"
+    echo
+    exit 1
+}
+
+# OS specific support (must be 'true' or 'false').
+cygwin=false
+msys=false
+darwin=false
+case "`uname`" in
+  CYGWIN* )
+    cygwin=true
+    ;;
+  Darwin* )
+    darwin=true
+    ;;
+  MINGW* )
+    msys=true
+    ;;
+esac
+
+# Attempt to set APP_HOME
+# Resolve links: $0 may be a link
+PRG="$0"
+# Need this for relative symlinks.
+while [ -h "$PRG" ] ; do
+    ls=`ls -ld "$PRG"`
+    link=`expr "$ls" : '.*-> \(.*\)$'`
+    if expr "$link" : '/.*' > /dev/null; then
+        PRG="$link"
+    else
+        PRG=`dirname "$PRG"`"/$link"
+    fi
+done
+SAVED="`pwd`"
+cd "`dirname \"$PRG\"`/" >/dev/null
+APP_HOME="`pwd -P`"
+cd "$SAVED" >/dev/null
+
+CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
+
+# Determine the Java command to use to start the JVM.
+if [ -n "$JAVA_HOME" ] ; then
+    if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
+        # IBM's JDK on AIX uses strange locations for the executables
+        JAVACMD="$JAVA_HOME/jre/sh/java"
+    else
+        JAVACMD="$JAVA_HOME/bin/java"
+    fi
+    if [ ! -x "$JAVACMD" ] ; then
+        die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
+
+Please set the JAVA_HOME variable in your environment to match the
+location of your Java installation."
+    fi
+else
+    JAVACMD="java"
+    which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
+
+Please set the JAVA_HOME variable in your environment to match the
+location of your Java installation."
+fi
+
+# Increase the maximum file descriptors if we can.
+if [ "$cygwin" = "false" -a "$darwin" = "false" ] ; then
+    MAX_FD_LIMIT=`ulimit -H -n`
+    if [ $? -eq 0 ] ; then
+        if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then
+            MAX_FD="$MAX_FD_LIMIT"
+        fi
+        ulimit -n $MAX_FD
+        if [ $? -ne 0 ] ; then
+            warn "Could not set maximum file descriptor limit: $MAX_FD"
+        fi
+    else
+        warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT"
+    fi
+fi
+
+# For Darwin, add options to specify how the application appears in the dock
+if $darwin; then
+    GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\""
+fi
+
+# For Cygwin, switch paths to Windows format before running java
+if $cygwin ; then
+    APP_HOME=`cygpath --path --mixed "$APP_HOME"`
+    CLASSPATH=`cygpath --path --mixed "$CLASSPATH"`
+    JAVACMD=`cygpath --unix "$JAVACMD"`
+
+    # We build the pattern for arguments to be converted via cygpath
+    ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null`
+    SEP=""
+    for dir in $ROOTDIRSRAW ; do
+        ROOTDIRS="$ROOTDIRS$SEP$dir"
+        SEP="|"
+    done
+    OURCYGPATTERN="(^($ROOTDIRS))"
+    # Add a user-defined pattern to the cygpath arguments
+    if [ "$GRADLE_CYGPATTERN" != "" ] ; then
+        OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)"
+    fi
+    # Now convert the arguments - kludge to limit ourselves to /bin/sh
+    i=0
+    for arg in "$@" ; do
+        CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -`
+        CHECK2=`echo "$arg"|egrep -c "^-"`                                 ### Determine if an option
+
+        if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then                    ### Added a condition
+            eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"`
+        else
+            eval `echo args$i`="\"$arg\""
+        fi
+        i=$((i+1))
+    done
+    case $i in
+        (0) set -- ;;
+        (1) set -- "$args0" ;;
+        (2) set -- "$args0" "$args1" ;;
+        (3) set -- "$args0" "$args1" "$args2" ;;
+        (4) set -- "$args0" "$args1" "$args2" "$args3" ;;
+        (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;;
+        (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;;
+        (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;;
+        (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;;
+        (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;;
+    esac
+fi
+
+# Split up the JVM_OPTS And GRADLE_OPTS values into an array, following the shell quoting and substitution rules
+function splitJvmOpts() {
+    JVM_OPTS=("$@")
+}
+eval splitJvmOpts $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS
+JVM_OPTS[${#JVM_OPTS[*]}]="-Dorg.gradle.appname=$APP_BASE_NAME"
+
+exec "$JAVACMD" "${JVM_OPTS[@]}" -classpath "$CLASSPATH" org.gradle.wrapper.GradleWrapperMain "$@"
diff --git a/samples-flatfoot/ContentProviderSample/gradlew.bat b/samples-flatfoot/ContentProviderSample/gradlew.bat
new file mode 100644
index 0000000..aec9973
--- /dev/null
+++ b/samples-flatfoot/ContentProviderSample/gradlew.bat
@@ -0,0 +1,90 @@
+@if "%DEBUG%" == "" @echo off

+@rem ##########################################################################

+@rem

+@rem  Gradle startup script for Windows

+@rem

+@rem ##########################################################################

+

+@rem Set local scope for the variables with windows NT shell

+if "%OS%"=="Windows_NT" setlocal

+

+@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.

+set DEFAULT_JVM_OPTS=

+

+set DIRNAME=%~dp0

+if "%DIRNAME%" == "" set DIRNAME=.

+set APP_BASE_NAME=%~n0

+set APP_HOME=%DIRNAME%

+

+@rem Find java.exe

+if defined JAVA_HOME goto findJavaFromJavaHome

+

+set JAVA_EXE=java.exe

+%JAVA_EXE% -version >NUL 2>&1

+if "%ERRORLEVEL%" == "0" goto init

+

+echo.

+echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.

+echo.

+echo Please set the JAVA_HOME variable in your environment to match the

+echo location of your Java installation.

+

+goto fail

+

+:findJavaFromJavaHome

+set JAVA_HOME=%JAVA_HOME:"=%

+set JAVA_EXE=%JAVA_HOME%/bin/java.exe

+

+if exist "%JAVA_EXE%" goto init

+

+echo.

+echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%

+echo.

+echo Please set the JAVA_HOME variable in your environment to match the

+echo location of your Java installation.

+

+goto fail

+

+:init

+@rem Get command-line arguments, handling Windowz variants

+

+if not "%OS%" == "Windows_NT" goto win9xME_args

+if "%@eval[2+2]" == "4" goto 4NT_args

+

+:win9xME_args

+@rem Slurp the command line arguments.

+set CMD_LINE_ARGS=

+set _SKIP=2

+

+:win9xME_args_slurp

+if "x%~1" == "x" goto execute

+

+set CMD_LINE_ARGS=%*

+goto execute

+

+:4NT_args

+@rem Get arguments from the 4NT Shell from JP Software

+set CMD_LINE_ARGS=%$

+

+:execute

+@rem Setup the command line

+

+set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar

+

+@rem Execute Gradle

+"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS%

+

+:end

+@rem End local scope for the variables with windows NT shell

+if "%ERRORLEVEL%"=="0" goto mainEnd

+

+:fail

+rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of

+rem the _cmd.exe /c_ return code!

+if  not "" == "%GRADLE_EXIT_CONSOLE%" exit 1

+exit /b 1

+

+:mainEnd

+if "%OS%"=="Windows_NT" endlocal

+

+:omega

diff --git a/samples-flatfoot/ContentProviderSample/settings.gradle b/samples-flatfoot/ContentProviderSample/settings.gradle
new file mode 100644
index 0000000..e7b4def
--- /dev/null
+++ b/samples-flatfoot/ContentProviderSample/settings.gradle
@@ -0,0 +1 @@
+include ':app'
diff --git a/samples-flatfoot/GithubBrowser/.gitignore b/samples-flatfoot/GithubBrowser/.gitignore
new file mode 100644
index 0000000..39fb081
--- /dev/null
+++ b/samples-flatfoot/GithubBrowser/.gitignore
@@ -0,0 +1,9 @@
+*.iml
+.gradle
+/local.properties
+/.idea/workspace.xml
+/.idea/libraries
+.DS_Store
+/build
+/captures
+.externalNativeBuild
diff --git a/samples-flatfoot/GithubBrowser/app/.gitignore b/samples-flatfoot/GithubBrowser/app/.gitignore
new file mode 100644
index 0000000..796b96d
--- /dev/null
+++ b/samples-flatfoot/GithubBrowser/app/.gitignore
@@ -0,0 +1 @@
+/build
diff --git a/samples-flatfoot/GithubBrowser/app/build.gradle b/samples-flatfoot/GithubBrowser/app/build.gradle
new file mode 100644
index 0000000..b1f12ac
--- /dev/null
+++ b/samples-flatfoot/GithubBrowser/app/build.gradle
@@ -0,0 +1,70 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+apply plugin: 'com.android.application'
+
+android {
+    compileSdkVersion 25
+    buildToolsVersion "25.0.2"
+    defaultConfig {
+        applicationId "com.android.sample.githubbrowser"
+        minSdkVersion 14
+        targetSdkVersion 25
+        versionCode 1
+        versionName "1.0"
+        testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
+
+        javaCompileOptions {
+            annotationProcessorOptions {
+                arguments = ["room.schemaLocation": "$projectDir/schemas".toString()]
+            }
+        }
+    }
+    dataBinding {
+        enabled = true
+    }
+    buildTypes {
+        release {
+            minifyEnabled false
+            proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
+        }
+    }
+}
+
+dependencies {
+    compile fileTree(dir: 'libs', include: ['*.jar'])
+    androidTestCompile('com.android.support.test.espresso:espresso-core:2.2.2', {
+        exclude group: 'com.android.support', module: 'support-annotations'
+    })
+    compile "com.android.support:percent:$supportLibVersion"
+    compile "com.android.support:cardview-v7:$supportLibVersion"
+    compile "com.android.support:appcompat-v7:$supportLibVersion"
+    compile "com.android.support:design:$supportLibVersion"
+    compile 'com.squareup.retrofit2:retrofit:2.1.0'
+    compile 'com.squareup.retrofit2:converter-gson:2.1.0'
+    compile 'com.github.bumptech.glide:glide:3.7.0'
+    compile "com.android.support.room:runtime:$flatfootVersion"
+    compile "com.android.support.lifecycle:runtime:$flatfootVersion"
+    compile "com.android.support.lifecycle:extensions:$flatfootVersion"
+
+    compile "com.google.dagger:dagger:$daggerVersion"
+
+    annotationProcessor "com.google.dagger:dagger-compiler:$daggerVersion"
+    annotationProcessor "com.android.support.room:compiler:$flatfootVersion"
+    annotationProcessor "com.android.support.lifecycle:compiler:$flatfootVersion"
+
+    testCompile 'junit:junit:4.12'
+}
diff --git a/samples-flatfoot/GithubBrowser/app/proguard-rules.pro b/samples-flatfoot/GithubBrowser/app/proguard-rules.pro
new file mode 100644
index 0000000..99e1cd4
--- /dev/null
+++ b/samples-flatfoot/GithubBrowser/app/proguard-rules.pro
@@ -0,0 +1,35 @@
+# Add project specific ProGuard rules here.
+# By default, the flags in this file are appended to flags specified
+# in /Users/kirillg/Android/sdk/tools/proguard/proguard-android.txt
+# You can edit the include path and order by changing the proguardFiles
+# directive in build.gradle.
+#
+# For more details, see
+#   http://developer.android.com/guide/developing/tools/proguard.html
+
+# Add any project specific keep options here:
+
+# If your project uses WebView with JS, uncomment the following
+# and specify the fully qualified class name to the JavaScript interface
+# class:
+#-keepclassmembers class fqcn.of.javascript.interface.for.webview {
+#   public *;
+#}
+
+# Platform calls Class.forName on types which do not exist on Android to determine platform.
+-dontnote retrofit2.Platform
+# Platform used when running on RoboVM on iOS. Will not be used at runtime.
+-dontnote retrofit2.Platform$IOS$MainThreadExecutor
+# Platform used when running on Java 8 VMs. Will not be used at runtime.
+-dontwarn retrofit2.Platform$Java8
+# Retain generic type information for use by reflection by converters and adapters.
+-keepattributes Signature
+# Retain declared checked exceptions for use by a Proxy instance.
+-keepattributes Exceptions
+
+-keep public class * implements com.bumptech.glide.module.GlideModule
+-keep public enum com.bumptech.glide.load.resource.bitmap.ImageHeaderParser$** {
+  **[] $VALUES;
+  public *;
+}
+-keepresourcexmlelements manifest/application/meta-data@value=GlideModule
\ No newline at end of file
diff --git a/samples-flatfoot/GithubBrowser/app/schemas/com.android.sample.githubbrowser.db.GithubDatabase/1.json b/samples-flatfoot/GithubBrowser/app/schemas/com.android.sample.githubbrowser.db.GithubDatabase/1.json
new file mode 100644
index 0000000..1409973
--- /dev/null
+++ b/samples-flatfoot/GithubBrowser/app/schemas/com.android.sample.githubbrowser.db.GithubDatabase/1.json
@@ -0,0 +1,393 @@
+{
+  "formatVersion": 1,
+  "database": {
+    "version": 1,
+    "identityHash": "b1b39c866af4b7b44d7ea3c6202c4352",
+    "entities": [
+      {
+        "tableName": "PersonData",
+        "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`login` TEXT, `id` TEXT, `avatar_url` TEXT, `repos_url` TEXT, `name` TEXT, `company` TEXT, `blog` TEXT, `location` TEXT, `email` TEXT, `public_repos` INTEGER, `followers` INTEGER, `following` INTEGER, `created_at` TEXT, PRIMARY KEY(`login`))",
+        "fields": [
+          {
+            "fieldPath": "login",
+            "columnName": "login",
+            "affinity": "TEXT"
+          },
+          {
+            "fieldPath": "id",
+            "columnName": "id",
+            "affinity": "TEXT"
+          },
+          {
+            "fieldPath": "avatar_url",
+            "columnName": "avatar_url",
+            "affinity": "TEXT"
+          },
+          {
+            "fieldPath": "repos_url",
+            "columnName": "repos_url",
+            "affinity": "TEXT"
+          },
+          {
+            "fieldPath": "name",
+            "columnName": "name",
+            "affinity": "TEXT"
+          },
+          {
+            "fieldPath": "company",
+            "columnName": "company",
+            "affinity": "TEXT"
+          },
+          {
+            "fieldPath": "blog",
+            "columnName": "blog",
+            "affinity": "TEXT"
+          },
+          {
+            "fieldPath": "location",
+            "columnName": "location",
+            "affinity": "TEXT"
+          },
+          {
+            "fieldPath": "email",
+            "columnName": "email",
+            "affinity": "TEXT"
+          },
+          {
+            "fieldPath": "public_repos",
+            "columnName": "public_repos",
+            "affinity": "INTEGER"
+          },
+          {
+            "fieldPath": "followers",
+            "columnName": "followers",
+            "affinity": "INTEGER"
+          },
+          {
+            "fieldPath": "following",
+            "columnName": "following",
+            "affinity": "INTEGER"
+          },
+          {
+            "fieldPath": "created_at",
+            "columnName": "created_at",
+            "affinity": "TEXT"
+          }
+        ],
+        "primaryKey": {
+          "columnNames": [
+            "login"
+          ],
+          "autoGenerate": false
+        },
+        "indices": []
+      },
+      {
+        "tableName": "SearchQueryData",
+        "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`searchQuery` TEXT, `searchKind` INTEGER, `timestamp` INTEGER, `indexOfLastFetchedPage` INTEGER, `numberOfFetchedItems` INTEGER, `hasNoMoreData` INTEGER, PRIMARY KEY(`searchQuery`, `searchKind`))",
+        "fields": [
+          {
+            "fieldPath": "searchQuery",
+            "columnName": "searchQuery",
+            "affinity": "TEXT"
+          },
+          {
+            "fieldPath": "searchKind",
+            "columnName": "searchKind",
+            "affinity": "INTEGER"
+          },
+          {
+            "fieldPath": "timestamp",
+            "columnName": "timestamp",
+            "affinity": "INTEGER"
+          },
+          {
+            "fieldPath": "indexOfLastFetchedPage",
+            "columnName": "indexOfLastFetchedPage",
+            "affinity": "INTEGER"
+          },
+          {
+            "fieldPath": "numberOfFetchedItems",
+            "columnName": "numberOfFetchedItems",
+            "affinity": "INTEGER"
+          },
+          {
+            "fieldPath": "hasNoMoreData",
+            "columnName": "hasNoMoreData",
+            "affinity": "INTEGER"
+          }
+        ],
+        "primaryKey": {
+          "columnNames": [
+            "searchQuery",
+            "searchKind"
+          ],
+          "autoGenerate": false
+        },
+        "indices": []
+      },
+      {
+        "tableName": "GeneralRepoSearchData",
+        "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`searchQuery` TEXT, `resultIndex` INTEGER, `repoId` TEXT, PRIMARY KEY(`searchQuery`, `resultIndex`, `repoId`))",
+        "fields": [
+          {
+            "fieldPath": "searchQuery",
+            "columnName": "searchQuery",
+            "affinity": "TEXT"
+          },
+          {
+            "fieldPath": "resultIndex",
+            "columnName": "resultIndex",
+            "affinity": "INTEGER"
+          },
+          {
+            "fieldPath": "repoId",
+            "columnName": "repoId",
+            "affinity": "TEXT"
+          }
+        ],
+        "primaryKey": {
+          "columnNames": [
+            "searchQuery",
+            "resultIndex",
+            "repoId"
+          ],
+          "autoGenerate": false
+        },
+        "indices": []
+      },
+      {
+        "tableName": "RepositoryData",
+        "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` TEXT, `name` TEXT, `full_name` TEXT, `description` TEXT, `created_at` TEXT, `stargazers_count` INTEGER, `language` TEXT, `forks_count` INTEGER, `open_issues_count` INTEGER, `subscribers_count` INTEGER, `owner_login` TEXT, `owner_id` TEXT, `owner_avatar_url` TEXT, `owner_repos_url` TEXT, `owner_name` TEXT, `owner_company` TEXT, `owner_blog` TEXT, `owner_location` TEXT, `owner_email` TEXT, `owner_public_repos` INTEGER, `owner_followers` INTEGER, `owner_following` INTEGER, `owner_created_at` TEXT, `organization_login` TEXT, `organization_id` TEXT, `organization_avatar_url` TEXT, `organization_repos_url` TEXT, `organization_name` TEXT, `organization_company` TEXT, `organization_blog` TEXT, `organization_location` TEXT, `organization_email` TEXT, `organization_public_repos` INTEGER, `organization_followers` INTEGER, `organization_following` INTEGER, `organization_created_at` TEXT, PRIMARY KEY(`id`))",
+        "fields": [
+          {
+            "fieldPath": "id",
+            "columnName": "id",
+            "affinity": "TEXT"
+          },
+          {
+            "fieldPath": "name",
+            "columnName": "name",
+            "affinity": "TEXT"
+          },
+          {
+            "fieldPath": "full_name",
+            "columnName": "full_name",
+            "affinity": "TEXT"
+          },
+          {
+            "fieldPath": "description",
+            "columnName": "description",
+            "affinity": "TEXT"
+          },
+          {
+            "fieldPath": "created_at",
+            "columnName": "created_at",
+            "affinity": "TEXT"
+          },
+          {
+            "fieldPath": "stargazers_count",
+            "columnName": "stargazers_count",
+            "affinity": "INTEGER"
+          },
+          {
+            "fieldPath": "language",
+            "columnName": "language",
+            "affinity": "TEXT"
+          },
+          {
+            "fieldPath": "forks_count",
+            "columnName": "forks_count",
+            "affinity": "INTEGER"
+          },
+          {
+            "fieldPath": "open_issues_count",
+            "columnName": "open_issues_count",
+            "affinity": "INTEGER"
+          },
+          {
+            "fieldPath": "subscribers_count",
+            "columnName": "subscribers_count",
+            "affinity": "INTEGER"
+          },
+          {
+            "fieldPath": "owner.login",
+            "columnName": "owner_login",
+            "affinity": "TEXT"
+          },
+          {
+            "fieldPath": "owner.id",
+            "columnName": "owner_id",
+            "affinity": "TEXT"
+          },
+          {
+            "fieldPath": "owner.avatar_url",
+            "columnName": "owner_avatar_url",
+            "affinity": "TEXT"
+          },
+          {
+            "fieldPath": "owner.repos_url",
+            "columnName": "owner_repos_url",
+            "affinity": "TEXT"
+          },
+          {
+            "fieldPath": "owner.name",
+            "columnName": "owner_name",
+            "affinity": "TEXT"
+          },
+          {
+            "fieldPath": "owner.company",
+            "columnName": "owner_company",
+            "affinity": "TEXT"
+          },
+          {
+            "fieldPath": "owner.blog",
+            "columnName": "owner_blog",
+            "affinity": "TEXT"
+          },
+          {
+            "fieldPath": "owner.location",
+            "columnName": "owner_location",
+            "affinity": "TEXT"
+          },
+          {
+            "fieldPath": "owner.email",
+            "columnName": "owner_email",
+            "affinity": "TEXT"
+          },
+          {
+            "fieldPath": "owner.public_repos",
+            "columnName": "owner_public_repos",
+            "affinity": "INTEGER"
+          },
+          {
+            "fieldPath": "owner.followers",
+            "columnName": "owner_followers",
+            "affinity": "INTEGER"
+          },
+          {
+            "fieldPath": "owner.following",
+            "columnName": "owner_following",
+            "affinity": "INTEGER"
+          },
+          {
+            "fieldPath": "owner.created_at",
+            "columnName": "owner_created_at",
+            "affinity": "TEXT"
+          },
+          {
+            "fieldPath": "organization.login",
+            "columnName": "organization_login",
+            "affinity": "TEXT"
+          },
+          {
+            "fieldPath": "organization.id",
+            "columnName": "organization_id",
+            "affinity": "TEXT"
+          },
+          {
+            "fieldPath": "organization.avatar_url",
+            "columnName": "organization_avatar_url",
+            "affinity": "TEXT"
+          },
+          {
+            "fieldPath": "organization.repos_url",
+            "columnName": "organization_repos_url",
+            "affinity": "TEXT"
+          },
+          {
+            "fieldPath": "organization.name",
+            "columnName": "organization_name",
+            "affinity": "TEXT"
+          },
+          {
+            "fieldPath": "organization.company",
+            "columnName": "organization_company",
+            "affinity": "TEXT"
+          },
+          {
+            "fieldPath": "organization.blog",
+            "columnName": "organization_blog",
+            "affinity": "TEXT"
+          },
+          {
+            "fieldPath": "organization.location",
+            "columnName": "organization_location",
+            "affinity": "TEXT"
+          },
+          {
+            "fieldPath": "organization.email",
+            "columnName": "organization_email",
+            "affinity": "TEXT"
+          },
+          {
+            "fieldPath": "organization.public_repos",
+            "columnName": "organization_public_repos",
+            "affinity": "INTEGER"
+          },
+          {
+            "fieldPath": "organization.followers",
+            "columnName": "organization_followers",
+            "affinity": "INTEGER"
+          },
+          {
+            "fieldPath": "organization.following",
+            "columnName": "organization_following",
+            "affinity": "INTEGER"
+          },
+          {
+            "fieldPath": "organization.created_at",
+            "columnName": "organization_created_at",
+            "affinity": "TEXT"
+          }
+        ],
+        "primaryKey": {
+          "columnNames": [
+            "id"
+          ],
+          "autoGenerate": false
+        },
+        "indices": []
+      },
+      {
+        "tableName": "ContributorSearchData",
+        "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`searchQuery` TEXT, `resultIndex` INTEGER, `contributorId` TEXT, `contributions` INTEGER, PRIMARY KEY(`searchQuery`, `resultIndex`, `contributorId`))",
+        "fields": [
+          {
+            "fieldPath": "searchQuery",
+            "columnName": "searchQuery",
+            "affinity": "TEXT"
+          },
+          {
+            "fieldPath": "resultIndex",
+            "columnName": "resultIndex",
+            "affinity": "INTEGER"
+          },
+          {
+            "fieldPath": "contributorId",
+            "columnName": "contributorId",
+            "affinity": "TEXT"
+          },
+          {
+            "fieldPath": "contributions",
+            "columnName": "contributions",
+            "affinity": "INTEGER"
+          }
+        ],
+        "primaryKey": {
+          "columnNames": [
+            "searchQuery",
+            "resultIndex",
+            "contributorId"
+          ],
+          "autoGenerate": false
+        },
+        "indices": []
+      }
+    ],
+    "setupQueries": [
+      "CREATE TABLE IF NOT EXISTS room_master_table (id INTEGER PRIMARY KEY,identity_hash TEXT)",
+      "INSERT OR REPLACE INTO room_master_table (id,identity_hash) VALUES(42, \"b1b39c866af4b7b44d7ea3c6202c4352\")"
+    ]
+  }
+}
\ No newline at end of file
diff --git a/samples-flatfoot/GithubBrowser/app/src/main/AndroidManifest.xml b/samples-flatfoot/GithubBrowser/app/src/main/AndroidManifest.xml
new file mode 100644
index 0000000..b2af65a
--- /dev/null
+++ b/samples-flatfoot/GithubBrowser/app/src/main/AndroidManifest.xml
@@ -0,0 +1,41 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  ~ Copyright (C) 2017 The Android Open Source Project
+  ~
+  ~ Licensed under the Apache License, Version 2.0 (the "License");
+  ~ you may not use this file except in compliance with the License.
+  ~ You may obtain a copy of the License at
+  ~
+  ~      http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing, software
+  ~ distributed under the License is distributed on an "AS IS" BASIS,
+  ~ WITHOUT 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 package="com.android.sample.githubbrowser"
+          xmlns:android="http://schemas.android.com/apk/res/android">
+    <uses-permission android:name="android.permission.INTERNET" />
+    <application
+        android:name=".GithubBrowserApp"
+        android:allowBackup="true"
+        android:icon="@mipmap/ic_launcher"
+        android:label="@string/app_name"
+        android:supportsRtl="true"
+        android:theme="@style/AppTheme">
+        <activity
+            android:name="com.android.sample.githubbrowser.MainActivity"
+            android:label="@string/app_name"
+            android:theme="@style/AppTheme.NoActionBar"
+            android:windowSoftInputMode="stateHidden">
+            <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-flatfoot/GithubBrowser/app/src/main/java/com/android/sample/githubbrowser/BaseActivity.java b/samples-flatfoot/GithubBrowser/app/src/main/java/com/android/sample/githubbrowser/BaseActivity.java
new file mode 100644
index 0000000..d66258d
--- /dev/null
+++ b/samples-flatfoot/GithubBrowser/app/src/main/java/com/android/sample/githubbrowser/BaseActivity.java
@@ -0,0 +1,52 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES 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.sample.githubbrowser;
+
+import android.support.v7.app.AppCompatActivity;
+
+import com.android.sample.githubbrowser.di.InjectableLifecycleProvider;
+import com.android.support.lifecycle.LifecycleRegistry;
+import com.android.support.lifecycle.LifecycleRegistryProvider;
+
+/**
+ * Temporary base activity that acts as lifecycle provider.
+ */
+public abstract class BaseActivity extends AppCompatActivity implements LifecycleRegistryProvider,
+        InjectableLifecycleProvider {
+
+    private final LifecycleRegistry mRegistry = new LifecycleRegistry(this);
+
+    @Override
+    public LifecycleRegistry getLifecycle() {
+        return mRegistry;
+    }
+}
\ No newline at end of file
diff --git a/samples-flatfoot/GithubBrowser/app/src/main/java/com/android/sample/githubbrowser/BaseFragment.java b/samples-flatfoot/GithubBrowser/app/src/main/java/com/android/sample/githubbrowser/BaseFragment.java
new file mode 100644
index 0000000..75c85d8
--- /dev/null
+++ b/samples-flatfoot/GithubBrowser/app/src/main/java/com/android/sample/githubbrowser/BaseFragment.java
@@ -0,0 +1,38 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES 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.sample.githubbrowser;
+
+import android.os.Bundle;
+
+import com.android.sample.githubbrowser.navigation.NavigationController;
+import com.android.support.lifecycle.LifecycleFragment;
+
+/**
+ * Base fragment w/ navigation controller access.
+ */
+public class BaseFragment extends LifecycleFragment {
+    private NavigationController mNavigationController;
+    @Override
+    public void onActivityCreated(Bundle savedInstanceState) {
+        super.onActivityCreated(savedInstanceState);
+        mNavigationController = ((MainActivity) getActivity()).getNavigationController();
+    }
+
+    public NavigationController getNavigationController() {
+        return mNavigationController;
+    }
+}
diff --git a/samples-flatfoot/GithubBrowser/app/src/main/java/com/android/sample/githubbrowser/EditUserDetailsFragment.java b/samples-flatfoot/GithubBrowser/app/src/main/java/com/android/sample/githubbrowser/EditUserDetailsFragment.java
new file mode 100644
index 0000000..21235e1
--- /dev/null
+++ b/samples-flatfoot/GithubBrowser/app/src/main/java/com/android/sample/githubbrowser/EditUserDetailsFragment.java
@@ -0,0 +1,150 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES 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.sample.githubbrowser;
+
+import android.app.Activity;
+import android.app.Dialog;
+import android.content.DialogInterface;
+import android.databinding.DataBindingUtil;
+import android.os.Bundle;
+import android.support.annotation.NonNull;
+import android.support.annotation.Nullable;
+import android.support.v4.app.DialogFragment;
+import android.support.v4.app.Fragment;
+import android.support.v7.app.AlertDialog;
+import android.view.LayoutInflater;
+import android.view.ViewGroup;
+import android.widget.EditText;
+
+import com.android.sample.githubbrowser.data.PersonData;
+import com.android.sample.githubbrowser.databinding.EditUserDetailsBinding;
+import com.android.sample.githubbrowser.viewmodel.PersonDataModel;
+import com.android.support.lifecycle.LifecycleRegistry;
+import com.android.support.lifecycle.LifecycleRegistryProvider;
+import com.android.support.lifecycle.Observer;
+import com.android.support.lifecycle.ViewModelStore;
+
+/**
+ * Edit user details fragment.
+ */
+public class EditUserDetailsFragment extends DialogFragment implements LifecycleRegistryProvider {
+    private static final String LOGIN = "editUser.login";
+    private static final int CODE_EDIT = 1;
+
+    private LifecycleRegistry mLifecycleRegistry = new LifecycleRegistry(this);
+    private PersonDataModel mPersonDataModel;
+
+    public EditUserDetailsFragment() {
+    }
+
+    public static EditUserDetailsFragment createFor(Fragment target, PersonData personData) {
+        EditUserDetailsFragment editUserDetailsFragment = new EditUserDetailsFragment();
+        Bundle editUserDetailsFragmentArgs = new Bundle();
+        editUserDetailsFragmentArgs.putString(EditUserDetailsFragment.LOGIN, personData.login);
+        editUserDetailsFragment.setArguments(editUserDetailsFragmentArgs);
+        editUserDetailsFragment.setTargetFragment(target, CODE_EDIT);
+        return editUserDetailsFragment;
+    }
+
+    @Override
+    public LifecycleRegistry getLifecycle() {
+        return mLifecycleRegistry;
+    }
+
+    @NonNull
+    @Override
+    public Dialog onCreateDialog(Bundle savedInstanceState) {
+        final String login = getArguments().getString(LOGIN);
+
+        // Configure the dialog to pass the data back when "OK" button is clicked
+        AlertDialog.Builder editBuilder = new AlertDialog.Builder(getContext())
+                .setTitle("Edit details")
+                .setPositiveButton(R.string.ok, new DialogInterface.OnClickListener() {
+                    @Override
+                    public void onClick(DialogInterface dialogInterface, int i) {
+                        // Ask the model to update the two fields on the user
+                        mPersonDataModel.update(login, getCurrentEmail(),
+                                getCurrentLocation());
+                        getTargetFragment().onActivityResult(getTargetRequestCode(),
+                                Activity.RESULT_OK, null);
+                    }
+                })
+                .setNegativeButton(R.string.cancel, new DialogInterface.OnClickListener() {
+                    @Override
+                    public void onClick(DialogInterface dialogInterface, int i) {
+                        getTargetFragment().onActivityResult(getTargetRequestCode(),
+                                Activity.RESULT_CANCELED, null);
+                    }
+                });
+
+        // Inflate the main editor area and set it as custom view on the dialog
+        final LayoutInflater inflater = LayoutInflater.from(getContext());
+        final EditUserDetailsBinding binding = DataBindingUtil.inflate(
+                inflater, R.layout.edit_user_details, null, false);
+        final ViewGroup editor = (ViewGroup) binding.getRoot();
+        editBuilder.setView(editor);
+        final AlertDialog result = editBuilder.create();
+
+        // Get our view model instance and register ourselves to observe change to the
+        // user data. When a change is reported, update all UI elements based on the new
+        // data.
+        mPersonDataModel = ViewModelStore.get(this, login, PersonDataModel.class);
+        // Ask the model to load the data for this user. When the data becomes available (either
+        // immediately from the previous load or later on when it's fetched from remote API call),
+        // we will be notified since this fragment registered itself as an observer on the matching
+        // live data object.
+        mPersonDataModel.loadData(login, false);
+        mPersonDataModel.getPersonData().observe(this, new Observer<PersonData>() {
+            @Override
+            public void onChanged(@Nullable PersonData personData) {
+                if (!isDetached() && (personData != null)) {
+                    android.util.Log.e("GithubBrowser", "Got data for editing from model");
+                    getDialog().setTitle(personData.name);
+                    binding.setUser(personData);
+                    binding.executePendingBindings();
+                }
+            }
+        });
+
+        return result;
+    }
+
+    private String getCurrentEmail() {
+        EditText runtime = (EditText) getDialog().findViewById(R.id.email);
+        return runtime.getText().toString();
+    }
+
+    private String getCurrentLocation() {
+        EditText rated = (EditText) getDialog().findViewById(R.id.location);
+        return rated.getText().toString();
+    }
+}
diff --git a/samples-flatfoot/GithubBrowser/app/src/main/java/com/android/sample/githubbrowser/GetAuthTokenFragment.java b/samples-flatfoot/GithubBrowser/app/src/main/java/com/android/sample/githubbrowser/GetAuthTokenFragment.java
new file mode 100644
index 0000000..6cfbc95
--- /dev/null
+++ b/samples-flatfoot/GithubBrowser/app/src/main/java/com/android/sample/githubbrowser/GetAuthTokenFragment.java
@@ -0,0 +1,100 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES 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.sample.githubbrowser;
+
+import android.app.Dialog;
+import android.content.DialogInterface;
+import android.os.Bundle;
+import android.support.annotation.NonNull;
+import android.support.v4.app.DialogFragment;
+import android.support.v7.app.AlertDialog;
+import android.view.LayoutInflater;
+import android.view.ViewGroup;
+import android.widget.EditText;
+
+import com.android.sample.githubbrowser.di.InjectableLifecycleProvider;
+import com.android.sample.githubbrowser.di.LifecycleProviderComponent;
+import com.android.sample.githubbrowser.model.AuthTokenModel;
+import com.android.support.lifecycle.LifecycleRegistry;
+import com.android.support.lifecycle.LifecycleRegistryProvider;
+
+import javax.inject.Inject;
+
+/**
+ * UI for getting an auth token for Github API calls.
+ */
+public class GetAuthTokenFragment extends DialogFragment implements LifecycleRegistryProvider,
+        InjectableLifecycleProvider {
+    LifecycleRegistry mLifecycleRegistry = new LifecycleRegistry(this);
+    @Inject
+    AuthTokenModel mAuthTokenModel;
+
+    @Override
+    public LifecycleRegistry getLifecycle() {
+        return mLifecycleRegistry;
+    }
+
+    @Override
+    public void onActivityCreated(Bundle savedInstanceState) {
+        super.onActivityCreated(savedInstanceState);
+    }
+
+    @NonNull
+    @Override
+    public Dialog onCreateDialog(Bundle savedInstanceState) {
+        AlertDialog.Builder editBuilder = new AlertDialog.Builder(getContext())
+                .setTitle(R.string.auth_token_title)
+                .setPositiveButton(R.string.ok, new DialogInterface.OnClickListener() {
+                    @Override
+                    public void onClick(DialogInterface dialogInterface, int i) {
+                        mAuthTokenModel.saveToken(getCurrentAuthToken());
+                    }
+                })
+                .setNegativeButton(R.string.cancel, null);
+
+        LayoutInflater inflater = LayoutInflater.from(getContext());
+        ViewGroup editor = (ViewGroup) inflater.inflate(R.layout.get_auth_token, null, false);
+        editBuilder.setView(editor);
+
+        return editBuilder.create();
+    }
+
+    private String getCurrentAuthToken() {
+        EditText runtime = (EditText) getDialog().findViewById(R.id.token);
+        return runtime.getText().toString();
+    }
+
+    @Override
+    public void inject(LifecycleProviderComponent component) {
+        component.inject(this);
+    }
+}
diff --git a/samples-flatfoot/GithubBrowser/app/src/main/java/com/android/sample/githubbrowser/GithubBrowserApp.java b/samples-flatfoot/GithubBrowser/app/src/main/java/com/android/sample/githubbrowser/GithubBrowserApp.java
new file mode 100644
index 0000000..02918fd
--- /dev/null
+++ b/samples-flatfoot/GithubBrowser/app/src/main/java/com/android/sample/githubbrowser/GithubBrowserApp.java
@@ -0,0 +1,108 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES 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.sample.githubbrowser;
+
+import android.app.Activity;
+import android.app.Application;
+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.FragmentManager;
+
+import com.android.sample.githubbrowser.di.AppComponent;
+import com.android.sample.githubbrowser.di.AppModule;
+import com.android.sample.githubbrowser.di.DaggerAppComponent;
+import com.android.sample.githubbrowser.di.InjectableLifecycleProvider;
+import com.android.sample.githubbrowser.di.LifecycleProviderComponent;
+import com.android.sample.githubbrowser.di.LifecycleProviderModule;
+import com.android.support.lifecycle.LifecycleProvider;
+
+public class GithubBrowserApp extends Application {
+    AppComponent mAppComponent;
+
+    @Override
+    public void onCreate() {
+        super.onCreate();
+        mAppComponent = DaggerAppComponent.builder()
+                .appModule(new AppModule(this)).build();
+        registerActivityLifecycleCallbacks(new ActivityLifecycleCallbacksAdapter() {
+            @Override
+            public void onActivityCreated(Activity activity, Bundle bundle) {
+                tryInject(mAppComponent, activity);
+
+                if (activity instanceof FragmentActivity) {
+                    ((FragmentActivity) activity).getSupportFragmentManager()
+                            .registerFragmentLifecycleCallbacks(
+                                    new FragmentManager.FragmentLifecycleCallbacks() {
+                                        @Override
+                                        public void onFragmentPreAttached(FragmentManager fm,
+                                                Fragment f, Context context) {
+                                            tryInject(mAppComponent, f);
+                                        }
+                                    }, true);
+                }
+            }
+        });
+    }
+
+    public AppComponent getAppComponent() {
+        return mAppComponent;
+    }
+
+    private void tryInject(AppComponent appComponent, Object object) {
+        if (object instanceof LifecycleProvider
+                && object instanceof InjectableLifecycleProvider) {
+            final LifecycleProviderComponent component = appComponent
+                    .plus(new LifecycleProviderModule((LifecycleProvider) object));
+            ((InjectableLifecycleProvider) object).inject(component);
+        }
+    }
+
+    /**
+     * Empty activity callback impl.
+     */
+    private static class ActivityLifecycleCallbacksAdapter implements ActivityLifecycleCallbacks {
+        @Override
+        public void onActivityCreated(Activity activity, Bundle savedInstanceState) {
+        }
+
+        @Override
+        public void onActivityStarted(Activity activity) {
+        }
+
+        @Override
+        public void onActivityResumed(Activity activity) {
+        }
+
+        @Override
+        public void onActivityPaused(Activity activity) {
+        }
+
+        @Override
+        public void onActivityStopped(Activity activity) {
+        }
+
+        @Override
+        public void onActivitySaveInstanceState(Activity activity, Bundle outState) {
+        }
+
+        @Override
+        public void onActivityDestroyed(Activity activity) {
+        }
+    }
+}
diff --git a/samples-flatfoot/GithubBrowser/app/src/main/java/com/android/sample/githubbrowser/MainActivity.java b/samples-flatfoot/GithubBrowser/app/src/main/java/com/android/sample/githubbrowser/MainActivity.java
new file mode 100644
index 0000000..de5b9fe
--- /dev/null
+++ b/samples-flatfoot/GithubBrowser/app/src/main/java/com/android/sample/githubbrowser/MainActivity.java
@@ -0,0 +1,168 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES 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.sample.githubbrowser;
+
+import android.annotation.SuppressLint;
+import android.content.Context;
+import android.os.Bundle;
+import android.support.annotation.NonNull;
+import android.support.annotation.Nullable;
+import android.support.design.widget.Snackbar;
+import android.support.v4.app.Fragment;
+import android.support.v4.app.FragmentManager;
+import android.support.v7.widget.Toolbar;
+import android.text.TextUtils;
+import android.view.KeyEvent;
+import android.view.View;
+import android.view.inputmethod.InputMethodManager;
+import android.widget.EditText;
+
+import com.android.sample.githubbrowser.di.LifecycleProviderComponent;
+import com.android.sample.githubbrowser.model.AuthTokenModel;
+import com.android.sample.githubbrowser.navigation.NavigationController;
+import com.android.sample.githubbrowser.viewmodel.RepositorySearchModel;
+import com.android.support.lifecycle.Observer;
+import com.android.support.lifecycle.ViewModelStore;
+
+import javax.inject.Inject;
+
+/**
+ * Our main activity.
+ */
+public class MainActivity extends BaseActivity {
+    private static final String AUTH_TOKEN_FRAGMENT_TAG = "get_auth_token";
+    @Inject
+    AuthTokenModel mAuthTokenModel;
+    private NavigationController mNavigationController;
+
+    @Override
+    public void inject(LifecycleProviderComponent component) {
+        component.inject(this);
+    }
+
+    @SuppressLint("SetTextI18n")
+    @Override
+    protected void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+        setContentView(R.layout.activity_main);
+        mNavigationController = new NavigationController(this, getSupportFragmentManager(),
+                R.id.fragment_container);
+        Toolbar toolbar = (Toolbar) findViewById(R.id.toolbar);
+        setSupportActionBar(toolbar);
+        final EditText search = (EditText) toolbar.findViewById(R.id.search);
+        final RepositorySearchModel mainSearchModel = ViewModelStore.get(this,
+                RepositorySearchModel.class);
+
+        String currentSearch = search.getText().toString();
+        if (TextUtils.isEmpty(currentSearch)) {
+            search.setText("google");
+            currentSearch = "google";
+        }
+        mainSearchModel.setQuery(currentSearch, true);
+
+        // Check that the activity is using the layout version with
+        // the fragment_container FrameLayout
+        if (savedInstanceState == null) {
+            // Create a new Fragment to be placed in the activity layout
+            RepositoryListFragment mainFragment = new RepositoryListFragment();
+
+            // Add the fragment to the 'fragment_container' FrameLayout
+            getSupportFragmentManager().beginTransaction()
+                    .add(R.id.fragment_container, mainFragment, "main").commit();
+        }
+
+        search.setOnKeyListener(new View.OnKeyListener() {
+            public boolean onKey(View v, int keyCode, KeyEvent event) {
+                if ((event.getAction() == KeyEvent.ACTION_DOWN)
+                        && (keyCode == KeyEvent.KEYCODE_ENTER)) {
+                    String query = search.getText().toString();
+                    Snackbar.make(findViewById(R.id.col), "Searching for " + query,
+                            Snackbar.LENGTH_SHORT).show();
+
+                    // Dismiss keyboard
+                    InputMethodManager imm = (InputMethodManager) getSystemService(
+                            Context.INPUT_METHOD_SERVICE);
+                    imm.hideSoftInputFromWindow(search.getWindowToken(), 0);
+
+                    // Pop everything off of the stack except the first entry
+                    FragmentManager fragmentManager = getSupportFragmentManager();
+                    while (fragmentManager.getBackStackEntryCount() > 0) {
+                        fragmentManager.popBackStackImmediate();
+                    }
+
+                    // Perform search action on key press
+                    mainSearchModel.setQuery(query, false);
+                    return true;
+                }
+                return false;
+            }
+        });
+
+        mAuthTokenModel.getAuthTokenData().observe(this, new Observer<String>() {
+            @Override
+            public void onChanged(@Nullable String token) {
+                search.setEnabled(token != null);
+                // show get auth token fragment
+                // Pop everything off of the stack except the first entry
+                FragmentManager fragmentManager = getSupportFragmentManager();
+                GetAuthTokenFragment getAuthTokenFragment = getGetAuthTokenFragment(
+                        fragmentManager);
+                if (token == null) {
+                    if (!getAuthTokenFragment.isAdded()) {
+                        getAuthTokenFragment.show(fragmentManager, AUTH_TOKEN_FRAGMENT_TAG);
+                    }
+                } else {
+                    if (getAuthTokenFragment.isAdded()) {
+                        getAuthTokenFragment.dismiss();
+                    }
+                }
+            }
+        });
+    }
+
+    @NonNull
+    private GetAuthTokenFragment getGetAuthTokenFragment(FragmentManager fragmentManager) {
+        Fragment authTokenFragment = fragmentManager
+                .findFragmentByTag(AUTH_TOKEN_FRAGMENT_TAG);
+        GetAuthTokenFragment getAuthTokenFragment;
+        if (authTokenFragment instanceof GetAuthTokenFragment) {
+            getAuthTokenFragment = (GetAuthTokenFragment) authTokenFragment;
+        } else {
+            getAuthTokenFragment = new GetAuthTokenFragment();
+        }
+        return getAuthTokenFragment;
+    }
+
+    public NavigationController getNavigationController() {
+        return mNavigationController;
+    }
+}
diff --git a/samples-flatfoot/GithubBrowser/app/src/main/java/com/android/sample/githubbrowser/RepositoryDetailsFragment.java b/samples-flatfoot/GithubBrowser/app/src/main/java/com/android/sample/githubbrowser/RepositoryDetailsFragment.java
new file mode 100644
index 0000000..aad6eed
--- /dev/null
+++ b/samples-flatfoot/GithubBrowser/app/src/main/java/com/android/sample/githubbrowser/RepositoryDetailsFragment.java
@@ -0,0 +1,150 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES 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.sample.githubbrowser;
+
+import android.databinding.DataBindingUtil;
+import android.os.Bundle;
+import android.support.annotation.Nullable;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+
+import com.android.sample.githubbrowser.adapter.ContributorListAdapter;
+import com.android.sample.githubbrowser.adapter.LoadMoreCallback;
+import com.android.sample.githubbrowser.data.ContributorData;
+import com.android.sample.githubbrowser.data.PersonData;
+import com.android.sample.githubbrowser.data.RepositoryData;
+import com.android.sample.githubbrowser.databinding.FragmentRepoDetailsBinding;
+import com.android.sample.githubbrowser.di.InjectableLifecycleProvider;
+import com.android.sample.githubbrowser.di.LifecycleProviderComponent;
+import com.android.sample.githubbrowser.view.PersonClickCallback;
+import com.android.sample.githubbrowser.viewmodel.ContributorListModel;
+import com.android.sample.githubbrowser.viewmodel.RepositoryDataModel;
+import com.android.support.lifecycle.Observer;
+import com.android.support.lifecycle.ViewModelStore;
+
+import java.util.List;
+
+/**
+ * Fragment that shows details of a single repository, including the list of its contributors.
+ */
+public class RepositoryDetailsFragment extends BaseFragment implements
+        InjectableLifecycleProvider {
+    private static final String REPO_ID = "repoDetails.id";
+    private static final String REPO_FULL_NAME = "repoDetails.fullName";
+    private LifecycleProviderComponent mComponent;
+    private FragmentRepoDetailsBinding mBinding;
+
+    public RepositoryDetailsFragment() {
+    }
+
+    public static RepositoryDetailsFragment createFor(RepositoryData repo) {
+        RepositoryDetailsFragment fragment = new RepositoryDetailsFragment();
+        Bundle detailsFragmentArgs = new Bundle();
+        detailsFragmentArgs.putString(RepositoryDetailsFragment.REPO_ID, repo.id);
+        detailsFragmentArgs.putString(RepositoryDetailsFragment.REPO_FULL_NAME, repo.full_name);
+        fragment.setArguments(detailsFragmentArgs);
+        return fragment;
+    }
+
+    @Override
+    public View onCreateView(final LayoutInflater inflater, ViewGroup container,
+            Bundle savedInstanceState) {
+        mBinding = DataBindingUtil.inflate(
+                inflater, R.layout.fragment_repo_details, container, false, mComponent);
+        return mBinding.getRoot();
+    }
+
+    @Override
+    public void onActivityCreated(Bundle savedInstanceState) {
+        super.onActivityCreated(savedInstanceState);
+        RepositoryDataModel repositoryDataModel = ViewModelStore.get(this,
+                RepositoryDataModel.class);
+        final ContributorListModel contributorListModel = ViewModelStore.get(this,
+                ContributorListModel.class);
+
+        final String repoId = getArguments().getString(REPO_ID);
+        final String repoFullName = getArguments().getString(REPO_FULL_NAME);
+        setupRecyclerView(contributorListModel);
+        // Ask the model to load the data for this repository. When the data becomes available
+        // (either immediately from the previous load or later on when it's fetched from
+        // remote API call), we will be notified since this fragment registered itself as an
+        // observer on the matching live data object.
+        repositoryDataModel.loadData(repoId, repoFullName);
+
+        repositoryDataModel.getRepositoryData().observe(this, new Observer<RepositoryData>() {
+            @Override
+            public void onChanged(@Nullable final RepositoryData repositoryData) {
+                if (repositoryData != null) {
+                    // Bind the data on this fragment
+                    mBinding.setRepo(repositoryData);
+                    // TODO decompose this data
+                    String[] split = repositoryData.full_name.split("/");
+                    contributorListModel.setSearchTerms(split[0], repositoryData.name);
+                } else {
+                    contributorListModel.setSearchTerms(null, null);
+                }
+            }
+        });
+    }
+
+    private PersonClickCallback mPersonClickCallback = new PersonClickCallback() {
+        @Override
+        public void onClick(PersonData person) {
+            getNavigationController().openUserDetailsFragment(person);
+        }
+    };
+
+    private void setupRecyclerView(final ContributorListModel contributorListModel) {
+        final ContributorListAdapter adapter = new ContributorListAdapter(mComponent,
+                mPersonClickCallback,
+                new LoadMoreCallback() {
+                    @Override
+                    public void loadMore(int currentSize) {
+                        contributorListModel.fetchAtIndexIfNecessary(currentSize);
+                    }
+                });
+        contributorListModel.getContributorListLiveData().observe(this,
+                new Observer<List<ContributorData>>() {
+                    @Override
+                    public void onChanged(@Nullable List<ContributorData> contributorList) {
+                        adapter.setData(contributorList);
+                    }
+                });
+        mBinding.contributors.setAdapter(adapter);
+    }
+
+    @Override
+    public void inject(LifecycleProviderComponent component) {
+        mComponent = component;
+    }
+}
diff --git a/samples-flatfoot/GithubBrowser/app/src/main/java/com/android/sample/githubbrowser/RepositoryListFragment.java b/samples-flatfoot/GithubBrowser/app/src/main/java/com/android/sample/githubbrowser/RepositoryListFragment.java
new file mode 100644
index 0000000..049a6d2
--- /dev/null
+++ b/samples-flatfoot/GithubBrowser/app/src/main/java/com/android/sample/githubbrowser/RepositoryListFragment.java
@@ -0,0 +1,154 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES 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.sample.githubbrowser;
+
+import android.databinding.DataBindingUtil;
+import android.os.Bundle;
+import android.support.annotation.Nullable;
+import android.support.v7.widget.GridLayoutManager;
+import android.support.v7.widget.RecyclerView;
+import android.text.TextUtils;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+
+import com.android.sample.githubbrowser.adapter.LoadMoreCallback;
+import com.android.sample.githubbrowser.adapter.RepositoryListAdapter;
+import com.android.sample.githubbrowser.data.RepositoryData;
+import com.android.sample.githubbrowser.databinding.FragmentRepoListBinding;
+import com.android.sample.githubbrowser.di.InjectableLifecycleProvider;
+import com.android.sample.githubbrowser.di.LifecycleProviderComponent;
+import com.android.sample.githubbrowser.model.AuthTokenModel;
+import com.android.sample.githubbrowser.view.RepoClickCallback;
+import com.android.sample.githubbrowser.viewmodel.RepositoryListModel;
+import com.android.sample.githubbrowser.viewmodel.RepositorySearchModel;
+import com.android.support.lifecycle.LifecycleProvider;
+import com.android.support.lifecycle.Observer;
+import com.android.support.lifecycle.ViewModelStore;
+
+import java.util.List;
+
+import javax.inject.Inject;
+
+/**
+ * Fragment that shows the list of all repositories that match the current search term.
+ */
+public class RepositoryListFragment extends BaseFragment implements
+        InjectableLifecycleProvider {
+    @Inject
+    AuthTokenModel mAuthTokenModel;
+    FragmentRepoListBinding mBinding;
+    LifecycleProviderComponent mComponent;
+
+    @Override
+    public void inject(LifecycleProviderComponent component) {
+        mComponent = component;
+        component.inject(this);
+    }
+    @Override
+    public View onCreateView(final LayoutInflater inflater, ViewGroup container,
+            Bundle savedInstanceState) {
+        mBinding = DataBindingUtil.inflate(
+                inflater, R.layout.fragment_repo_list, container, false);
+        return mBinding.getRoot();
+    }
+
+    @Override
+    public void onActivityCreated(@Nullable Bundle savedInstanceState) {
+        super.onActivityCreated(savedInstanceState);
+        final RecyclerView recyclerView = mBinding.repoList;
+
+        // Get all the models that are needed for this fragment
+
+        // The model for our search query. Note that we are using the activity scope since
+        // that is where the search box "lives"
+        final RepositorySearchModel mainSearchModel = ViewModelStore.get(
+                (LifecycleProvider) getActivity(), RepositorySearchModel.class);
+        // The model for the list of repositories that are shown in this fragment.
+        final RepositoryListModel repositoryListModel = ViewModelStore.get(
+                this, RepositoryListModel.class);
+        // The model for auth token.
+        final RepositoryListAdapter adapter = new RepositoryListAdapter(mComponent,
+                new RepoClickCallback() {
+                    @Override
+                    public void onClick(RepositoryData repositoryData) {
+                        getNavigationController().openRepositoryDetailsFragment(repositoryData);
+                    }
+                },
+                new LoadMoreCallback() {
+                    @Override
+                    public void loadMore(int currentSize) {
+                        repositoryListModel.fetchAtIndexIfNecessary(currentSize);
+                    }
+                });
+        recyclerView.setAdapter(adapter);
+
+        // Wire changes in search query to update the list of repositories
+        mainSearchModel.getSearchQueryData().observe(this, new Observer<String>() {
+            @Override
+            public void onChanged(@Nullable String s) {
+                // When the main search query changes, update the list model with that query
+                // so that it starts loading new data.
+                repositoryListModel.setSearchTerm(s);
+                mBinding.setQuery(s);
+            }
+        });
+
+        repositoryListModel.getRepositoryListLiveData().observe(this,
+                new Observer<List<RepositoryData>>() {
+                    @Override
+                    public void onChanged(@Nullable List<RepositoryData> repoList) {
+                        adapter.setData(repoList);
+                    }
+                });
+
+        repositoryListModel.getStateLiveData().observe(this, new Observer<Integer>() {
+            @Override
+            public void onChanged(@Nullable Integer state) {
+                mBinding.setState(state);
+            }
+        });
+
+        final int columnCount = getContext().getResources().getInteger(R.integer.column_count);
+        recyclerView.setLayoutManager(new GridLayoutManager(getContext(), columnCount));
+
+        // Wire changes in auth token to continue loading the list of repositories
+        mAuthTokenModel.getAuthTokenData().observe(this, new Observer<String>() {
+            @Override
+            public void onChanged(@Nullable String s) {
+                if (!TextUtils.isEmpty(s)) {
+                    repositoryListModel.resumeLoading();
+                }
+            }
+        });
+    }
+}
diff --git a/samples-flatfoot/GithubBrowser/app/src/main/java/com/android/sample/githubbrowser/UserDetailsFragment.java b/samples-flatfoot/GithubBrowser/app/src/main/java/com/android/sample/githubbrowser/UserDetailsFragment.java
new file mode 100644
index 0000000..fe0acf8
--- /dev/null
+++ b/samples-flatfoot/GithubBrowser/app/src/main/java/com/android/sample/githubbrowser/UserDetailsFragment.java
@@ -0,0 +1,163 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES 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.sample.githubbrowser;
+
+import android.databinding.DataBindingUtil;
+import android.os.Bundle;
+import android.support.annotation.Nullable;
+import android.support.v7.widget.GridLayoutManager;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+
+import com.android.sample.githubbrowser.adapter.LoadMoreCallback;
+import com.android.sample.githubbrowser.adapter.RepositoryListAdapter;
+import com.android.sample.githubbrowser.data.PersonData;
+import com.android.sample.githubbrowser.data.RepositoryData;
+import com.android.sample.githubbrowser.databinding.FragmentUserDetailsBinding;
+import com.android.sample.githubbrowser.di.InjectableLifecycleProvider;
+import com.android.sample.githubbrowser.di.LifecycleProviderComponent;
+import com.android.sample.githubbrowser.view.PersonClickCallback;
+import com.android.sample.githubbrowser.view.RepoClickCallback;
+import com.android.sample.githubbrowser.viewmodel.PersonDataModel;
+import com.android.sample.githubbrowser.viewmodel.RepositoryListModel;
+import com.android.support.lifecycle.Observer;
+import com.android.support.lifecycle.ViewModelStore;
+
+import java.util.List;
+
+/**
+ * Fragment that shows details of a single user, including the list of their repositories.
+ */
+public class UserDetailsFragment extends BaseFragment implements InjectableLifecycleProvider {
+    private static final String USER_LOGIN = "userDetails.login";
+
+    private String mLogin;
+    private PersonDataModel mPersonDataModel;
+    private LifecycleProviderComponent mComponent;
+
+    public UserDetailsFragment() {
+    }
+
+    @Override
+    public void inject(LifecycleProviderComponent component) {
+        mComponent = component;
+    }
+
+    public static UserDetailsFragment createFor(PersonData person) {
+        UserDetailsFragment fragment = new UserDetailsFragment();
+        Bundle detailsFragmentArgs = new Bundle();
+        detailsFragmentArgs.putString(UserDetailsFragment.USER_LOGIN, person.login);
+        fragment.setArguments(detailsFragmentArgs);
+        return fragment;
+    }
+
+    @Override
+    public View onCreateView(final LayoutInflater inflater, ViewGroup container,
+            Bundle savedInstanceState) {
+        final FragmentUserDetailsBinding binding = DataBindingUtil.inflate(
+                inflater, R.layout.fragment_user_details, container, false, mComponent);
+
+        mLogin = getArguments().getString(USER_LOGIN);
+
+        // Get our view model instance and register ourselves to observe change to the
+        // full user data. When a change is reported, update all UI elements based on the new
+        // data.
+        mPersonDataModel = ViewModelStore.get(this, mLogin, PersonDataModel.class);
+        // Ask the model to load the data for this user. When the data becomes available (either
+        // immediately from the previous load or later on when it's fetched from remote API call),
+        // we will be notified since this fragment registered itself as an observer on the matching
+        // live data object.
+        // Note that the last parameter specifies that we're fine with getting partial data as
+        // quickly as possible.
+        mPersonDataModel.loadData(mLogin, false);
+        binding.setEditCallback(new PersonClickCallback() {
+            @Override
+            public void onClick(PersonData user) {
+                if (user == null) {
+                    return;
+                }
+                getNavigationController().openEditUserDetailsFragment(UserDetailsFragment.this,
+                        user);
+            }
+        });
+        mPersonDataModel.getPersonData().observe(this, new Observer<PersonData>() {
+            @Override
+            public void onChanged(@Nullable final PersonData personData) {
+                if (personData == null) {
+                    return;
+                }
+
+                // Populate as much info on this user as we can
+                binding.setUser(personData);
+                binding.executePendingBindings();
+
+                if (!personData.isFullData()) {
+                    // If we only have partial data, initiate a full load.
+                    mPersonDataModel.loadData(mLogin, true);
+                }
+            }
+        });
+
+        // Load the list of repositories for this user based on the passed login.
+        final RepositoryListModel repositoriesListModel = ViewModelStore.get(this,
+                RepositoryListModel.class);
+        repositoriesListModel.setSearchTerm(mLogin);
+
+        final RepositoryListAdapter adapter = new RepositoryListAdapter(mComponent,
+                new RepoClickCallback() {
+                    @Override
+                    public void onClick(RepositoryData repositoryData) {
+                        getNavigationController().openRepositoryDetailsFragment(repositoryData);
+                    }
+                },
+                new LoadMoreCallback() {
+                    @Override
+                    public void loadMore(int currentSize) {
+                        repositoriesListModel.fetchAtIndexIfNecessary(currentSize);
+                    }
+                });
+        binding.repositories.setAdapter(adapter);
+        repositoriesListModel.getRepositoryListLiveData().observe(this,
+                new Observer<List<RepositoryData>>() {
+                    @Override
+                    public void onChanged(@Nullable List<RepositoryData> data) {
+                        adapter.setData(data);
+                    }
+                });
+        final int columnCount = getContext().getResources().getInteger(
+                R.integer.column_count);
+        binding.repositories.setLayoutManager(new GridLayoutManager(getContext(), columnCount));
+
+        return binding.getRoot();
+    }
+}
diff --git a/samples-flatfoot/GithubBrowser/app/src/main/java/com/android/sample/githubbrowser/adapter/ContributorListAdapter.java b/samples-flatfoot/GithubBrowser/app/src/main/java/com/android/sample/githubbrowser/adapter/ContributorListAdapter.java
new file mode 100644
index 0000000..f8de8c6
--- /dev/null
+++ b/samples-flatfoot/GithubBrowser/app/src/main/java/com/android/sample/githubbrowser/adapter/ContributorListAdapter.java
@@ -0,0 +1,134 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES 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.sample.githubbrowser.adapter;
+
+import android.databinding.DataBindingUtil;
+import android.support.annotation.MainThread;
+import android.support.v7.util.DiffUtil;
+import android.support.v7.util.DiffUtil.DiffResult;
+import android.support.v7.widget.RecyclerView;
+import android.view.LayoutInflater;
+import android.view.ViewGroup;
+
+import com.android.sample.githubbrowser.R;
+import com.android.sample.githubbrowser.adapter.ContributorListAdapter.ContributorBindingHolder;
+import com.android.sample.githubbrowser.data.ContributorData;
+import com.android.sample.githubbrowser.databinding.UserRowBinding;
+import com.android.sample.githubbrowser.di.LifecycleProviderComponent;
+import com.android.sample.githubbrowser.view.PersonClickCallback;
+
+import java.util.Collections;
+import java.util.List;
+
+/**
+ * Adapter for the list of contributors.
+ */
+public class ContributorListAdapter extends RecyclerView.Adapter<ContributorBindingHolder> {
+    /**
+     * Holder for the data cell.
+     */
+    static class ContributorBindingHolder extends RecyclerView.ViewHolder {
+        private UserRowBinding mViewDataBinding;
+
+        ContributorBindingHolder(UserRowBinding viewDataBinding) {
+            super(viewDataBinding.getRoot());
+            mViewDataBinding = viewDataBinding;
+        }
+
+        public UserRowBinding getBinding() {
+            return mViewDataBinding;
+        }
+    }
+
+    private List<ContributorData> mCurrList;
+    private PersonClickCallback mPersonClickCallback;
+    private LoadMoreCallback mLoadMoreCallback;
+    private LifecycleProviderComponent mComponent;
+
+    public ContributorListAdapter(LifecycleProviderComponent component,
+            PersonClickCallback personClickCallback, LoadMoreCallback loadMoreCallback) {
+        mComponent = component;
+        mPersonClickCallback = personClickCallback;
+        mLoadMoreCallback = loadMoreCallback;
+    }
+
+    @MainThread
+    public void setData(final List<ContributorData> newList) {
+        if (newList == null) {
+            setData(Collections.<ContributorData>emptyList());
+            return;
+        }
+        if (mCurrList == null) {
+            mCurrList = newList;
+            notifyItemRangeInserted(0, newList.size());
+        } else {
+            DiffResult result = DiffUtil.calculateDiff(
+                new DiffUtilListCallback<ContributorData, String>(mCurrList, newList) {
+                    @Override
+                    String getId(ContributorData item) {
+                        return item.id;
+                    }
+                });
+            result.dispatchUpdatesTo(ContributorListAdapter.this);
+            mCurrList = newList;
+        }
+    }
+
+    @Override
+    public ContributorBindingHolder onCreateViewHolder(ViewGroup parent, int viewType) {
+        UserRowBinding binding = DataBindingUtil.inflate(
+                LayoutInflater.from(parent.getContext()),
+                R.layout.user_row, parent, false, mComponent);
+        binding.setCallback(mPersonClickCallback);
+        return new ContributorBindingHolder(binding);
+    }
+
+    @Override
+    public void onBindViewHolder(ContributorBindingHolder holder, final int position) {
+        final ContributorData data = mCurrList.get(position);
+
+        // Use data binding for wiring the data and the click handler
+        UserRowBinding binding = holder.getBinding();
+        binding.setContributor(data);
+        binding.executePendingBindings();
+
+        // Do we need to request another page?
+        if (position > (mCurrList.size() - 2)) {
+            mLoadMoreCallback.loadMore(mCurrList.size());
+        }
+    }
+
+    @Override
+    public int getItemCount() {
+        return mCurrList == null ? 0 : mCurrList.size();
+    }
+}
diff --git a/samples-flatfoot/GithubBrowser/app/src/main/java/com/android/sample/githubbrowser/adapter/DiffUtilListCallback.java b/samples-flatfoot/GithubBrowser/app/src/main/java/com/android/sample/githubbrowser/adapter/DiffUtilListCallback.java
new file mode 100644
index 0000000..4378267
--- /dev/null
+++ b/samples-flatfoot/GithubBrowser/app/src/main/java/com/android/sample/githubbrowser/adapter/DiffUtilListCallback.java
@@ -0,0 +1,61 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES 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.sample.githubbrowser.adapter;
+
+import android.support.v7.util.DiffUtil;
+
+import java.util.List;
+
+/**
+ * A DiffUtil callback implementation for lists which use equals for comparison.
+ * @param <T>
+ * @param <K>
+ */
+abstract class DiffUtilListCallback<T, K> extends DiffUtil.Callback {
+    private final List<T> mOldList;
+    private final List<T> mNewList;
+
+    DiffUtilListCallback(List<T> oldList, List<T> newList) {
+        mOldList = oldList;
+        mNewList = newList;
+    }
+
+    @Override
+    public int getOldListSize() {
+        return mOldList.size();
+    }
+
+    @Override
+    public int getNewListSize() {
+        return mNewList.size();
+    }
+
+    @Override
+    public boolean areItemsTheSame(int oldItemPosition, int newItemPosition) {
+        K oldId = getId(mOldList.get(oldItemPosition));
+        K newId = getId(mNewList.get(newItemPosition));
+        return oldId.equals(newId);
+    }
+
+    @Override
+    public boolean areContentsTheSame(int oldItemPosition, int newItemPosition) {
+        return mOldList.get(oldItemPosition).equals(
+                mNewList.get(newItemPosition));
+    }
+
+    abstract K getId(T item);
+}
diff --git a/samples-flatfoot/GithubBrowser/app/src/main/java/com/android/sample/githubbrowser/adapter/LoadMoreCallback.java b/samples-flatfoot/GithubBrowser/app/src/main/java/com/android/sample/githubbrowser/adapter/LoadMoreCallback.java
new file mode 100644
index 0000000..c1ebb52
--- /dev/null
+++ b/samples-flatfoot/GithubBrowser/app/src/main/java/com/android/sample/githubbrowser/adapter/LoadMoreCallback.java
@@ -0,0 +1,21 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES 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.sample.githubbrowser.adapter;
+
+public interface LoadMoreCallback {
+    void loadMore(int currentSize);
+}
diff --git a/samples-flatfoot/GithubBrowser/app/src/main/java/com/android/sample/githubbrowser/adapter/RepositoryListAdapter.java b/samples-flatfoot/GithubBrowser/app/src/main/java/com/android/sample/githubbrowser/adapter/RepositoryListAdapter.java
new file mode 100644
index 0000000..9bc23ef
--- /dev/null
+++ b/samples-flatfoot/GithubBrowser/app/src/main/java/com/android/sample/githubbrowser/adapter/RepositoryListAdapter.java
@@ -0,0 +1,137 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES 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.sample.githubbrowser.adapter;
+
+import android.databinding.DataBindingComponent;
+import android.databinding.DataBindingUtil;
+import android.support.annotation.MainThread;
+import android.support.v7.util.DiffUtil;
+import android.support.v7.util.DiffUtil.DiffResult;
+import android.support.v7.widget.RecyclerView;
+import android.view.LayoutInflater;
+import android.view.ViewGroup;
+
+import com.android.sample.githubbrowser.R;
+import com.android.sample.githubbrowser.adapter.RepositoryListAdapter.RepositoryBindingHolder;
+import com.android.sample.githubbrowser.data.RepositoryData;
+import com.android.sample.githubbrowser.databinding.RepositoryCardBinding;
+import com.android.sample.githubbrowser.view.RepoClickCallback;
+
+import java.util.Collections;
+import java.util.List;
+
+/**
+ * Adapter for a list of repositories.
+ */
+public class RepositoryListAdapter extends RecyclerView.Adapter<RepositoryBindingHolder> {
+    /**
+     * Holder for the data cell.
+     */
+    static class RepositoryBindingHolder extends RecyclerView.ViewHolder {
+        private RepositoryCardBinding mViewDataBinding;
+
+        RepositoryBindingHolder(RepositoryCardBinding viewDataBinding) {
+            super(viewDataBinding.getRoot());
+            mViewDataBinding = viewDataBinding;
+        }
+
+        public RepositoryCardBinding getBinding() {
+            return mViewDataBinding;
+        }
+    }
+
+    private List<RepositoryData> mCurrList;
+    private DataBindingComponent mComponent;
+    private RepoClickCallback mRepoClickCallback;
+    private LoadMoreCallback mLoadMoreCallback;
+
+    /**
+     * Creates an adapter.
+     */
+    public RepositoryListAdapter(android.databinding.DataBindingComponent component,
+            RepoClickCallback callback, LoadMoreCallback loadMoreCallback) {
+        mComponent = component;
+        mRepoClickCallback = callback;
+        mLoadMoreCallback = loadMoreCallback;
+    }
+
+    @MainThread
+    public void setData(final List<RepositoryData> newList) {
+        if (newList == null) {
+            setData(Collections.<RepositoryData>emptyList());
+            return;
+        }
+        if (mCurrList == null) {
+            mCurrList = newList;
+            notifyItemRangeInserted(0, newList.size());
+        } else {
+            DiffResult result = DiffUtil.calculateDiff(
+                    new DiffUtilListCallback<RepositoryData, String>(mCurrList, newList) {
+                        @Override
+                        String getId(RepositoryData item) {
+                            return item.id;
+                        }
+                    });
+            result.dispatchUpdatesTo(RepositoryListAdapter.this);
+            mCurrList = newList;
+        }
+    }
+
+    @Override
+    public RepositoryBindingHolder onCreateViewHolder(ViewGroup parent, int viewType) {
+        RepositoryCardBinding binding = DataBindingUtil.inflate(
+                LayoutInflater.from(parent.getContext()),
+                R.layout.repository_card, parent, false, mComponent);
+        binding.setRepoClickCallback(mRepoClickCallback);
+        return new RepositoryBindingHolder(binding);
+    }
+
+    @Override
+    public void onBindViewHolder(RepositoryBindingHolder holder, final int position) {
+        final RepositoryData data = mCurrList.get(position);
+
+        // Use data binding for wiring the data and the click handler
+        RepositoryCardBinding binding = holder.getBinding();
+        binding.setRepo(data);
+        binding.executePendingBindings();
+
+        // Do we need to request another page?
+        if (position > (mCurrList.size() - 2)) {
+            mLoadMoreCallback.loadMore(mCurrList.size());
+        }
+    }
+
+    @Override
+    public int getItemCount() {
+        return mCurrList == null ? 0 : mCurrList.size();
+    }
+}
diff --git a/samples-flatfoot/GithubBrowser/app/src/main/java/com/android/sample/githubbrowser/data/ContributorData.java b/samples-flatfoot/GithubBrowser/app/src/main/java/com/android/sample/githubbrowser/data/ContributorData.java
new file mode 100644
index 0000000..c170902
--- /dev/null
+++ b/samples-flatfoot/GithubBrowser/app/src/main/java/com/android/sample/githubbrowser/data/ContributorData.java
@@ -0,0 +1,64 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES 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.sample.githubbrowser.data;
+
+import com.android.support.room.Entity;
+
+/**
+ * Contributor data object.
+ */
+@Entity
+public class ContributorData extends PersonData {
+    public int contributions;
+
+    public ContributorData() {
+    }
+
+    @Override
+    public boolean equals(Object o) {
+        if (this == o) return true;
+        if (o == null || getClass() != o.getClass()) return false;
+        if (!super.equals(o)) return false;
+
+        ContributorData that = (ContributorData) o;
+
+        return contributions == that.contributions;
+
+    }
+
+    @Override
+    public int hashCode() {
+        int result = super.hashCode();
+        result = 31 * result + contributions;
+        return result;
+    }
+}
diff --git a/samples-flatfoot/GithubBrowser/app/src/main/java/com/android/sample/githubbrowser/data/ContributorSearchData.java b/samples-flatfoot/GithubBrowser/app/src/main/java/com/android/sample/githubbrowser/data/ContributorSearchData.java
new file mode 100644
index 0000000..20bdd3f
--- /dev/null
+++ b/samples-flatfoot/GithubBrowser/app/src/main/java/com/android/sample/githubbrowser/data/ContributorSearchData.java
@@ -0,0 +1,48 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES 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.sample.githubbrowser.data;
+
+import com.android.support.room.Entity;
+
+/**
+ * Contains information on a single locally persisted entry from contributor list.
+ */
+@Entity(primaryKeys = {"searchQuery", "resultIndex", "contributorId"})
+public class ContributorSearchData {
+    public String searchQuery;
+    public int resultIndex;
+    public String contributorId;
+    public int contributions;
+
+    public ContributorSearchData() {
+    }
+}
diff --git a/samples-flatfoot/GithubBrowser/app/src/main/java/com/android/sample/githubbrowser/data/GeneralRepoSearchData.java b/samples-flatfoot/GithubBrowser/app/src/main/java/com/android/sample/githubbrowser/data/GeneralRepoSearchData.java
new file mode 100644
index 0000000..1c89cca
--- /dev/null
+++ b/samples-flatfoot/GithubBrowser/app/src/main/java/com/android/sample/githubbrowser/data/GeneralRepoSearchData.java
@@ -0,0 +1,47 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES 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.sample.githubbrowser.data;
+
+import com.android.support.room.Entity;
+
+/**
+ * Contains information on a single locally persisted entry from repo search.
+ */
+@Entity(primaryKeys = {"searchQuery", "resultIndex", "repoId"})
+public class GeneralRepoSearchData {
+    public String searchQuery;
+    public int resultIndex;
+    public String repoId;
+
+    public GeneralRepoSearchData() {
+    }
+}
diff --git a/samples-flatfoot/GithubBrowser/app/src/main/java/com/android/sample/githubbrowser/data/PersonData.java b/samples-flatfoot/GithubBrowser/app/src/main/java/com/android/sample/githubbrowser/data/PersonData.java
new file mode 100644
index 0000000..ef37c69
--- /dev/null
+++ b/samples-flatfoot/GithubBrowser/app/src/main/java/com/android/sample/githubbrowser/data/PersonData.java
@@ -0,0 +1,111 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES 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.sample.githubbrowser.data;
+
+import android.text.TextUtils;
+
+import com.android.support.room.Entity;
+import com.android.support.room.PrimaryKey;
+
+/**
+ * Person data object.
+ */
+@Entity
+public class PersonData {
+    @PrimaryKey public String login;
+    public String id;
+    public String avatar_url;
+    public String repos_url;
+    public String name;
+    public String company;
+    public String blog;
+    public String location;
+    public String email;
+    public int public_repos;
+    public int followers;
+    public int following;
+    public String created_at;
+
+    public PersonData() {
+    }
+
+    public boolean isFullData() {
+        return !TextUtils.isEmpty(created_at);
+    }
+
+    @Override
+    public boolean equals(Object o) {
+        if (this == o) return true;
+        if (o == null || getClass() != o.getClass()) return false;
+
+        PersonData that = (PersonData) o;
+
+        if (public_repos != that.public_repos) return false;
+        if (followers != that.followers) return false;
+        if (following != that.following) return false;
+        if (login != null ? !login.equals(that.login) : that.login != null) return false;
+        if (id != null ? !id.equals(that.id) : that.id != null) return false;
+        if (avatar_url != null ? !avatar_url.equals(that.avatar_url) : that.avatar_url != null) {
+            return false;
+        }
+        if (repos_url != null ? !repos_url.equals(that.repos_url) : that.repos_url != null) {
+            return false;
+        }
+        if (name != null ? !name.equals(that.name) : that.name != null) return false;
+        if (company != null ? !company.equals(that.company) : that.company != null) return false;
+        if (blog != null ? !blog.equals(that.blog) : that.blog != null) return false;
+        if (location != null ? !location.equals(that.location) : that.location != null) {
+            return false;
+        }
+        if (email != null ? !email.equals(that.email) : that.email != null) return false;
+        return created_at != null ? created_at.equals(that.created_at) : that.created_at == null;
+
+    }
+
+    @Override
+    public int hashCode() {
+        int result = login != null ? login.hashCode() : 0;
+        result = 31 * result + (id != null ? id.hashCode() : 0);
+        result = 31 * result + (avatar_url != null ? avatar_url.hashCode() : 0);
+        result = 31 * result + (repos_url != null ? repos_url.hashCode() : 0);
+        result = 31 * result + (name != null ? name.hashCode() : 0);
+        result = 31 * result + (company != null ? company.hashCode() : 0);
+        result = 31 * result + (blog != null ? blog.hashCode() : 0);
+        result = 31 * result + (location != null ? location.hashCode() : 0);
+        result = 31 * result + (email != null ? email.hashCode() : 0);
+        result = 31 * result + public_repos;
+        result = 31 * result + followers;
+        result = 31 * result + following;
+        result = 31 * result + (created_at != null ? created_at.hashCode() : 0);
+        return result;
+    }
+}
diff --git a/samples-flatfoot/GithubBrowser/app/src/main/java/com/android/sample/githubbrowser/data/RepositoryData.java b/samples-flatfoot/GithubBrowser/app/src/main/java/com/android/sample/githubbrowser/data/RepositoryData.java
new file mode 100644
index 0000000..0bb64cc
--- /dev/null
+++ b/samples-flatfoot/GithubBrowser/app/src/main/java/com/android/sample/githubbrowser/data/RepositoryData.java
@@ -0,0 +1,110 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES 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.sample.githubbrowser.data;
+
+import com.android.support.room.Decompose;
+import com.android.support.room.Entity;
+import com.android.support.room.PrimaryKey;
+import com.android.support.room.RoomWarnings;
+
+/**
+ * Repository data object.
+ */
+@Entity
+public class RepositoryData {
+    @PrimaryKey public String id;
+    public String name;
+    public String full_name;
+    @SuppressWarnings(RoomWarnings.PRIMARY_KEY_FROM_DECOMPOSED_IS_DROPPED)
+    @Decompose(prefix = "owner_") public PersonData owner;
+    public String description;
+    public String created_at;
+    public int stargazers_count;
+    public String language;
+    public int forks_count;
+    public int open_issues_count;
+    @SuppressWarnings(RoomWarnings.PRIMARY_KEY_FROM_DECOMPOSED_IS_DROPPED)
+    @Decompose(prefix = "organization_") public PersonData organization;
+    public int subscribers_count;
+
+    public RepositoryData() {
+    }
+
+    @Override
+    public boolean equals(Object o) {
+        if (this == o) return true;
+        if (o == null || getClass() != o.getClass()) return false;
+
+        RepositoryData that = (RepositoryData) o;
+
+        if (stargazers_count != that.stargazers_count) return false;
+        if (forks_count != that.forks_count) return false;
+        if (open_issues_count != that.open_issues_count) return false;
+        if (subscribers_count != that.subscribers_count) return false;
+        if (id != null ? !id.equals(that.id) : that.id != null) return false;
+        if (name != null ? !name.equals(that.name) : that.name != null) return false;
+        if (full_name != null ? !full_name.equals(that.full_name) : that.full_name != null) {
+            return false;
+        }
+        if (owner != null ? !owner.equals(that.owner) : that.owner != null) return false;
+        if (description != null ? !description.equals(that.description)
+                : that.description != null) {
+            return false;
+        }
+        if (created_at != null ? !created_at.equals(that.created_at) : that.created_at != null) {
+            return false;
+        }
+        if (language != null ? !language.equals(that.language) : that.language != null) {
+            return false;
+        }
+        return organization != null ? organization.equals(that.organization)
+                : that.organization == null;
+
+    }
+
+    @Override
+    public int hashCode() {
+        int result = id != null ? id.hashCode() : 0;
+        result = 31 * result + (name != null ? name.hashCode() : 0);
+        result = 31 * result + (full_name != null ? full_name.hashCode() : 0);
+        result = 31 * result + (owner != null ? owner.hashCode() : 0);
+        result = 31 * result + (description != null ? description.hashCode() : 0);
+        result = 31 * result + (created_at != null ? created_at.hashCode() : 0);
+        result = 31 * result + stargazers_count;
+        result = 31 * result + (language != null ? language.hashCode() : 0);
+        result = 31 * result + forks_count;
+        result = 31 * result + open_issues_count;
+        result = 31 * result + (organization != null ? organization.hashCode() : 0);
+        result = 31 * result + subscribers_count;
+        return result;
+    }
+}
diff --git a/samples-flatfoot/GithubBrowser/app/src/main/java/com/android/sample/githubbrowser/data/SearchQueryData.java b/samples-flatfoot/GithubBrowser/app/src/main/java/com/android/sample/githubbrowser/data/SearchQueryData.java
new file mode 100644
index 0000000..c9d230f
--- /dev/null
+++ b/samples-flatfoot/GithubBrowser/app/src/main/java/com/android/sample/githubbrowser/data/SearchQueryData.java
@@ -0,0 +1,53 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES 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.sample.githubbrowser.data;
+
+import com.android.support.room.Entity;
+
+/**
+ * Contains information about locally persisted data on a single paginable query.
+ */
+@Entity(primaryKeys = {"searchQuery", "searchKind"})
+public class SearchQueryData {
+    public static final int GENERAL_REPOSITORIES = 0;
+    public static final int REPOSITORY_CONTRIBUTORS = 1;
+
+    public String searchQuery;
+    public int searchKind;
+    public long timestamp;
+    public int indexOfLastFetchedPage;
+    public int numberOfFetchedItems;
+    public boolean hasNoMoreData;
+
+    public SearchQueryData() {
+    }
+}
diff --git a/samples-flatfoot/GithubBrowser/app/src/main/java/com/android/sample/githubbrowser/databinding/DataBindingAdapters.java b/samples-flatfoot/GithubBrowser/app/src/main/java/com/android/sample/githubbrowser/databinding/DataBindingAdapters.java
new file mode 100644
index 0000000..d70ae64
--- /dev/null
+++ b/samples-flatfoot/GithubBrowser/app/src/main/java/com/android/sample/githubbrowser/databinding/DataBindingAdapters.java
@@ -0,0 +1,99 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES 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.sample.githubbrowser.databinding;
+
+import android.databinding.BindingAdapter;
+import android.support.annotation.StringRes;
+import android.support.v4.app.Fragment;
+import android.text.TextUtils;
+import android.view.View;
+import android.widget.ImageView;
+import android.widget.TextView;
+
+import com.bumptech.glide.Glide;
+import com.bumptech.glide.RequestManager;
+
+import java.text.ParseException;
+import java.text.SimpleDateFormat;
+import java.util.Date;
+import java.util.Locale;
+
+public class DataBindingAdapters {
+    private static SimpleDateFormat sJsonDateParser = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss",
+            Locale.ENGLISH);
+
+    RequestManager mRequestManager;
+    public DataBindingAdapters(RequestManager requestManager) {
+        mRequestManager = requestManager;
+    }
+
+    @SuppressWarnings("WeakerAccess")
+    @BindingAdapter({"imageUrl"})
+    public void loadImage(ImageView imageView, String url) {
+        if (!TextUtils.isEmpty(url)) {
+            mRequestManager.load(url).fitCenter().crossFade().into(imageView);
+        } else {
+            imageView.setImageBitmap(null);
+        }
+    }
+
+    /**
+     * Displays formatted date given a JSON-originating date.
+     */
+    @BindingAdapter("jsonDate")
+    public static void formatDate(TextView textView, String jsonDate) {
+        if (TextUtils.isEmpty(jsonDate)) {
+            return;
+        }
+        try {
+            textView.setText(SimpleDateFormat.getDateInstance(SimpleDateFormat.SHORT).format(
+                    sJsonDateParser.parse(jsonDate)));
+        } catch (ParseException pe) {
+            // WTF
+        }
+    }
+
+    /**
+     * Displays formatted date given a JSON-originating date.
+     */
+    @BindingAdapter({"stringRes", "jsonDate"})
+    public static void formatDateWithString(TextView textView, @StringRes int stringRes,
+            String jsonDate) {
+        if (TextUtils.isEmpty(jsonDate)) {
+            return;
+        }
+        try {
+            Date date = sJsonDateParser.parse(jsonDate);
+            String formattedDate = SimpleDateFormat.getDateInstance(
+                    SimpleDateFormat.SHORT).format(date);
+            textView.setText(textView.getResources().getString(stringRes,
+                    formattedDate));
+        } catch (ParseException pe) {
+            // WTF
+        }
+    }
+
+    @BindingAdapter("visibleInvisible")
+    public static void changeVisiblity(View view, boolean value) {
+        view.setVisibility(value ? View.VISIBLE : View.INVISIBLE);
+    }
+
+    @BindingAdapter("visibleGone")
+    public static void showHide(View view, boolean value) {
+        view.setVisibility(value ? View.VISIBLE : View.GONE);
+    }
+}
diff --git a/samples-flatfoot/GithubBrowser/app/src/main/java/com/android/sample/githubbrowser/db/GithubDao.java b/samples-flatfoot/GithubBrowser/app/src/main/java/com/android/sample/githubbrowser/db/GithubDao.java
new file mode 100644
index 0000000..f4868e6
--- /dev/null
+++ b/samples-flatfoot/GithubBrowser/app/src/main/java/com/android/sample/githubbrowser/db/GithubDao.java
@@ -0,0 +1,111 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES 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.sample.githubbrowser.db;
+
+import com.android.sample.githubbrowser.data.ContributorData;
+import com.android.sample.githubbrowser.data.ContributorSearchData;
+import com.android.sample.githubbrowser.data.GeneralRepoSearchData;
+import com.android.sample.githubbrowser.data.PersonData;
+import com.android.sample.githubbrowser.data.RepositoryData;
+import com.android.sample.githubbrowser.data.SearchQueryData;
+import com.android.support.lifecycle.LiveData;
+import com.android.support.room.Dao;
+import com.android.support.room.Insert;
+import com.android.support.room.OnConflictStrategy;
+import com.android.support.room.Query;
+
+import java.util.List;
+
+/**
+ * Data access object for github data table.
+ */
+@Dao
+public interface GithubDao {
+    /**
+     * Load full data for a person based on the login.
+     */
+    @Query("select * from persondata where login = ?")
+    LiveData<PersonData> loadPerson(String login);
+
+    /**
+     * Insert or update full data for a person.
+     */
+    @Insert(onConflict = OnConflictStrategy.REPLACE)
+    void insertOrReplacePerson(PersonData personData);
+
+    @Query("UPDATE PersonData SET email = :email, location = :location WHERE login = :login")
+    void updateUser(String login, String email, String location);
+
+    /** Load search data for the specified query. */
+    @Query("select * from searchquerydata where searchQuery = :searchQuery"
+            + " AND searchKind = :searchKind")
+    SearchQueryData getSearchQueryData(String searchQuery, int searchKind);
+
+    /** Updates search data. */
+    @Insert(onConflict = OnConflictStrategy.REPLACE)
+    void update(SearchQueryData searchQueryData);
+
+    /** Inserts or updates metadata for results of repository search. */
+    @Insert(onConflict = OnConflictStrategy.REPLACE)
+    void insert(GeneralRepoSearchData[] generalRepoSearchDataArray);
+
+    /** Inserts or updates the repository data objects. */
+    @Insert(onConflict = OnConflictStrategy.REPLACE)
+    void insert(RepositoryData[] repoDataArray);
+
+    /** Insert or update full data for a repository. */
+    @Insert(onConflict = OnConflictStrategy.REPLACE)
+    void insertOrReplaceRepository(RepositoryData repositoryData);
+
+    /** Loads full data for a repository. */
+    @Query("select * from repositorydata where id = ?")
+    LiveData<RepositoryData> loadRepository(String id);
+
+    /** Inserts or updates metadata for results of contributor search. */
+    @Insert(onConflict = OnConflictStrategy.REPLACE)
+    void insert(ContributorSearchData[] contributorSearchDataArray);
+
+    /** Inserts or updates the person data objects. */
+    @Insert(onConflict = OnConflictStrategy.REPLACE)
+    void insert(PersonData[] personDataArray);
+
+    /**
+     * Loads repository results for the specified query.
+     */
+    @Query("SELECT r.*, MIN(qr.resultIndex) as resultIndex from repositorydata r, "
+            + "generalreposearchdata qr, searchquerydata q"
+            + "    WHERE q.searchQuery = qr.searchQuery"
+            + "          AND q.searchKind = " + SearchQueryData.GENERAL_REPOSITORIES
+            + "          AND r.id = qr.repoId"
+            + "          AND q.searchQuery = ?"
+            + "          GROUP BY r.id"
+            + "          ORDER BY resultIndex")
+    LiveData<List<RepositoryData>> getRepositories(String searchQuery);
+
+    /**
+     * Loads contributor results for the specified repository.
+     */
+    @Query("SELECT p.*, qr.contributions, MIN(qr.resultIndex) as resultIndex from persondata p, "
+            + "contributorsearchdata qr, searchquerydata q"
+            + "    WHERE q.searchQuery = qr.searchQuery"
+            + "          AND q.searchKind = " + SearchQueryData.REPOSITORY_CONTRIBUTORS
+            + "          AND p.id = qr.contributorId"
+            + "          AND q.searchQuery = ?"
+            + "          GROUP BY p.id"
+            + "          ORDER BY resultIndex")
+    LiveData<List<ContributorData>> getContributors(String repoName);
+}
diff --git a/samples-flatfoot/GithubBrowser/app/src/main/java/com/android/sample/githubbrowser/db/GithubDatabase.java b/samples-flatfoot/GithubBrowser/app/src/main/java/com/android/sample/githubbrowser/db/GithubDatabase.java
new file mode 100644
index 0000000..91f7fbe
--- /dev/null
+++ b/samples-flatfoot/GithubBrowser/app/src/main/java/com/android/sample/githubbrowser/db/GithubDatabase.java
@@ -0,0 +1,37 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES 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.sample.githubbrowser.db;
+
+import com.android.sample.githubbrowser.data.ContributorSearchData;
+import com.android.sample.githubbrowser.data.GeneralRepoSearchData;
+import com.android.sample.githubbrowser.data.PersonData;
+import com.android.sample.githubbrowser.data.RepositoryData;
+import com.android.sample.githubbrowser.data.SearchQueryData;
+import com.android.support.room.Database;
+import com.android.support.room.RoomDatabase;
+
+/**
+ * Database for Github entities.
+ */
+@Database(version = 1, entities = {PersonData.class, SearchQueryData.class,
+        GeneralRepoSearchData.class, RepositoryData.class, ContributorSearchData.class})
+public abstract class GithubDatabase extends RoomDatabase {
+    /**
+     * Gets the data access object.
+     */
+    public abstract GithubDao getGithubDao();
+}
diff --git a/samples-flatfoot/GithubBrowser/app/src/main/java/com/android/sample/githubbrowser/di/AppComponent.java b/samples-flatfoot/GithubBrowser/app/src/main/java/com/android/sample/githubbrowser/di/AppComponent.java
new file mode 100644
index 0000000..ef7d173
--- /dev/null
+++ b/samples-flatfoot/GithubBrowser/app/src/main/java/com/android/sample/githubbrowser/di/AppComponent.java
@@ -0,0 +1,39 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES 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.sample.githubbrowser.di;
+
+import com.android.sample.githubbrowser.viewmodel.ContributorListModel;
+import com.android.sample.githubbrowser.viewmodel.PersonDataModel;
+import com.android.sample.githubbrowser.viewmodel.RepositoryDataModel;
+import com.android.sample.githubbrowser.viewmodel.RepositoryListModel;
+
+import javax.inject.Singleton;
+
+import dagger.Component;
+
+@Singleton
+@Component(modules = AppModule.class)
+public interface AppComponent {
+    LifecycleProviderComponent plus(LifecycleProviderModule lifecycleProviderModule);
+    void inject(PersonDataModel personDataModel);
+
+    void inject(RepositoryListModel repositoryListModel);
+
+    void inject(ContributorListModel contributorListModel);
+
+    void inject(RepositoryDataModel repositoryDataModel);
+}
diff --git a/samples-flatfoot/GithubBrowser/app/src/main/java/com/android/sample/githubbrowser/di/AppModule.java b/samples-flatfoot/GithubBrowser/app/src/main/java/com/android/sample/githubbrowser/di/AppModule.java
new file mode 100644
index 0000000..e3d6a11
--- /dev/null
+++ b/samples-flatfoot/GithubBrowser/app/src/main/java/com/android/sample/githubbrowser/di/AppModule.java
@@ -0,0 +1,62 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES 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.sample.githubbrowser.di;
+
+import android.app.Application;
+
+import com.android.sample.githubbrowser.db.GithubDatabase;
+import com.android.sample.githubbrowser.model.AuthTokenModel;
+import com.android.sample.githubbrowser.network.GithubNetworkManager;
+import com.android.support.room.Room;
+
+import javax.inject.Singleton;
+
+import dagger.Module;
+import dagger.Provides;
+
+@Module
+public class AppModule {
+    private final Application mApplication;
+
+    public AppModule(Application application) {
+        mApplication = application;
+    }
+
+    @Singleton
+    @Provides
+    public GithubNetworkManager provideGithubNetworkManager(AuthTokenModel authTokenModel) {
+        return new GithubNetworkManager(authTokenModel);
+    }
+
+    @Singleton
+    @Provides
+    public AuthTokenModel provideAuthTokenModel(Application application) {
+        return new AuthTokenModel(application);
+    }
+
+    @Provides
+    public Application provideApplication() {
+        return mApplication;
+    }
+
+    @Singleton
+    @Provides
+    public GithubDatabase provideGithubDatabase(Application application) {
+        return Room.databaseBuilder(application, GithubDatabase.class, "github.db")
+                .build();
+    }
+}
diff --git a/samples-flatfoot/GithubBrowser/app/src/main/java/com/android/sample/githubbrowser/di/InjectableLifecycleProvider.java b/samples-flatfoot/GithubBrowser/app/src/main/java/com/android/sample/githubbrowser/di/InjectableLifecycleProvider.java
new file mode 100644
index 0000000..5cfd517
--- /dev/null
+++ b/samples-flatfoot/GithubBrowser/app/src/main/java/com/android/sample/githubbrowser/di/InjectableLifecycleProvider.java
@@ -0,0 +1,21 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES 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.sample.githubbrowser.di;
+
+public interface InjectableLifecycleProvider {
+    void inject(LifecycleProviderComponent component);
+}
diff --git a/samples-flatfoot/GithubBrowser/app/src/main/java/com/android/sample/githubbrowser/di/LifecycleProviderComponent.java b/samples-flatfoot/GithubBrowser/app/src/main/java/com/android/sample/githubbrowser/di/LifecycleProviderComponent.java
new file mode 100644
index 0000000..de379c8
--- /dev/null
+++ b/samples-flatfoot/GithubBrowser/app/src/main/java/com/android/sample/githubbrowser/di/LifecycleProviderComponent.java
@@ -0,0 +1,36 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES 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.sample.githubbrowser.di;
+
+import android.databinding.DataBindingComponent;
+
+import com.android.sample.githubbrowser.GetAuthTokenFragment;
+import com.android.sample.githubbrowser.MainActivity;
+import com.android.sample.githubbrowser.RepositoryListFragment;
+import com.android.sample.githubbrowser.databinding.DataBindingAdapters;
+
+import dagger.Subcomponent;
+
+@LifecycleProviderScope
+@Subcomponent(modules = {LifecycleProviderModule.class})
+public interface LifecycleProviderComponent extends android.databinding.DataBindingComponent {
+    void inject(MainActivity mainActivity);
+    void inject(GetAuthTokenFragment getAuthTokenFragment);
+    void inject(RepositoryListFragment repositoryListFragment);
+    @Override
+    DataBindingAdapters getDataBindingAdapters();
+}
diff --git a/samples-flatfoot/GithubBrowser/app/src/main/java/com/android/sample/githubbrowser/di/LifecycleProviderModule.java b/samples-flatfoot/GithubBrowser/app/src/main/java/com/android/sample/githubbrowser/di/LifecycleProviderModule.java
new file mode 100644
index 0000000..75b18eb
--- /dev/null
+++ b/samples-flatfoot/GithubBrowser/app/src/main/java/com/android/sample/githubbrowser/di/LifecycleProviderModule.java
@@ -0,0 +1,92 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES 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.sample.githubbrowser.di;
+
+import android.app.Application;
+import android.content.Context;
+
+import com.android.sample.githubbrowser.databinding.DataBindingAdapters;
+import com.android.support.lifecycle.LifecycleObserver;
+import com.android.support.lifecycle.LifecycleProvider;
+import com.android.support.lifecycle.OnLifecycleEvent;
+
+import com.bumptech.glide.Glide;
+import com.bumptech.glide.RequestManager;
+import com.bumptech.glide.manager.Lifecycle;
+import com.bumptech.glide.manager.LifecycleListener;
+import com.bumptech.glide.manager.RequestManagerRetriever;
+import com.bumptech.glide.manager.RequestManagerTreeNode;
+
+import java.util.Collections;
+import java.util.Set;
+
+import javax.inject.Singleton;
+
+import dagger.Module;
+import dagger.Provides;
+
+@Module
+public class LifecycleProviderModule {
+    private final LifecycleProvider mLifecycleProvider;
+
+    public LifecycleProviderModule(LifecycleProvider lifecycleProvider) {
+        mLifecycleProvider = lifecycleProvider;
+    }
+
+    @Provides
+    @Singleton
+    public LifecycleProvider provideLifecycleProvider() {
+        return mLifecycleProvider;
+    }
+
+    @Provides
+    @LifecycleProviderScope
+    public DataBindingAdapters provideAdapters(RequestManager requestManager) {
+        return new DataBindingAdapters(requestManager);
+    }
+
+    @Provides
+    @LifecycleProviderScope
+    public RequestManager provideGlideRequestManager(Application application) {
+        return new RequestManager(application, new Lifecycle() {
+            @Override
+            public void addListener(final LifecycleListener listener) {
+                mLifecycleProvider.getLifecycle().addObserver(new LifecycleObserver() {
+                    @OnLifecycleEvent(com.android.support.lifecycle.Lifecycle.ON_START)
+                    public void onStart() {
+                        listener.onStart();
+                    }
+
+                    @OnLifecycleEvent(com.android.support.lifecycle.Lifecycle.ON_STOP)
+                    public void onStop() {
+                        listener.onStop();
+                    }
+
+                    @OnLifecycleEvent(com.android.support.lifecycle.Lifecycle.ON_DESTROY)
+                    public void onDestroy() {
+                        listener.onDestroy();
+                    }
+                });
+            }
+        }, new RequestManagerTreeNode() {
+            @Override
+            public Set<RequestManager> getDescendants() {
+                return Collections.emptySet();
+            }
+        });
+    }
+}
diff --git a/samples-flatfoot/GithubBrowser/app/src/main/java/com/android/sample/githubbrowser/di/LifecycleProviderScope.java b/samples-flatfoot/GithubBrowser/app/src/main/java/com/android/sample/githubbrowser/di/LifecycleProviderScope.java
new file mode 100644
index 0000000..87dbb46
--- /dev/null
+++ b/samples-flatfoot/GithubBrowser/app/src/main/java/com/android/sample/githubbrowser/di/LifecycleProviderScope.java
@@ -0,0 +1,27 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES 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.sample.githubbrowser.di;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+
+import javax.inject.Scope;
+
+@Scope
+@Retention(RetentionPolicy.RUNTIME)
+public @interface LifecycleProviderScope {
+}
diff --git a/samples-flatfoot/GithubBrowser/app/src/main/java/com/android/sample/githubbrowser/model/AuthTokenModel.java b/samples-flatfoot/GithubBrowser/app/src/main/java/com/android/sample/githubbrowser/model/AuthTokenModel.java
new file mode 100644
index 0000000..8b06c33
--- /dev/null
+++ b/samples-flatfoot/GithubBrowser/app/src/main/java/com/android/sample/githubbrowser/model/AuthTokenModel.java
@@ -0,0 +1,96 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES 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.sample.githubbrowser.model;
+
+import android.app.Application;
+import android.content.Context;
+import android.content.SharedPreferences;
+import android.os.AsyncTask;
+
+import com.android.sample.githubbrowser.BuildConfig;
+import com.android.support.lifecycle.LiveData;
+
+import javax.inject.Singleton;
+
+/**
+ * Model for the auth token.
+ */
+@Singleton
+public class AuthTokenModel {
+    private static final String AUTH_TOKEN_KEY = "auth_token";
+
+    private LiveData<String> mAuthToken = new LiveData<>();
+    private final SharedPreferences mSharedPreferences;
+
+    public AuthTokenModel(Application application) {
+         mSharedPreferences = application
+                .getSharedPreferences(BuildConfig.APPLICATION_ID, Context.MODE_PRIVATE);
+        loadAsync();
+    }
+
+    public void saveToken(String token) {
+        saveAsync(token);
+        mAuthToken.postValue(token);
+    }
+
+    private void loadAsync() {
+        new AsyncTask<Void, Void, Void>() {
+            @Override
+            protected Void doInBackground(Void... voids) {
+                String token = mSharedPreferences.getString(AUTH_TOKEN_KEY, null);
+                mAuthToken.postValue(token);
+                return null;
+            }
+        }.execute();
+    }
+
+    private void saveAsync(String token) {
+        new AsyncTask<String, Void, Void>() {
+            @Override
+            protected Void doInBackground(String... tokens) {
+                mSharedPreferences.edit().putString(AUTH_TOKEN_KEY, tokens[0]).apply();
+                return null;
+            }
+        }.execute(token);
+    }
+
+    /**
+     * Returns the {@link LiveData} object that wraps the auth token.
+     */
+    public LiveData<String> getAuthTokenData() {
+        return mAuthToken;
+    }
+
+    public void clearToken() {
+        saveToken(null);
+    }
+}
diff --git a/samples-flatfoot/GithubBrowser/app/src/main/java/com/android/sample/githubbrowser/navigation/NavigationController.java b/samples-flatfoot/GithubBrowser/app/src/main/java/com/android/sample/githubbrowser/navigation/NavigationController.java
new file mode 100644
index 0000000..a4a66ba
--- /dev/null
+++ b/samples-flatfoot/GithubBrowser/app/src/main/java/com/android/sample/githubbrowser/navigation/NavigationController.java
@@ -0,0 +1,71 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES 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.sample.githubbrowser.navigation;
+
+import android.support.v4.app.Fragment;
+import android.support.v4.app.FragmentManager;
+import android.support.v4.app.FragmentTransaction;
+
+import com.android.sample.githubbrowser.EditUserDetailsFragment;
+import com.android.sample.githubbrowser.RepositoryDetailsFragment;
+import com.android.sample.githubbrowser.UserDetailsFragment;
+import com.android.sample.githubbrowser.data.PersonData;
+import com.android.sample.githubbrowser.data.RepositoryData;
+import com.android.support.lifecycle.Lifecycle;
+import com.android.support.lifecycle.LifecycleProvider;
+
+public class NavigationController {
+    private final FragmentManager mFragmentManager;
+    private final int mHostViewId;
+    private final Lifecycle mLifecycle;
+
+    public NavigationController(LifecycleProvider lifecycleProvider,
+            FragmentManager fragmentManager, int hostViewId) {
+        mLifecycle = lifecycleProvider.getLifecycle();
+        mFragmentManager = fragmentManager;
+        mHostViewId = hostViewId;
+    }
+
+    private boolean isActive() {
+        return mLifecycle.getCurrentState() >= Lifecycle.STARTED;
+    }
+
+    public void openRepositoryDetailsFragment(RepositoryData repo) {
+        RepositoryDetailsFragment fragment = RepositoryDetailsFragment.createFor(repo);
+        FragmentTransaction transaction = mFragmentManager.beginTransaction();
+        transaction.add(mHostViewId, fragment, "repoDetails:" + repo.id);
+        transaction.addToBackStack("repoDetails:" + repo.id);
+        transaction.commitAllowingStateLoss();
+    }
+
+    public void openUserDetailsFragment(PersonData person) {
+        UserDetailsFragment fragment = UserDetailsFragment.createFor(person);
+        FragmentTransaction transaction = mFragmentManager.beginTransaction();
+        transaction.add(mHostViewId, fragment, "userDetails:" + person.login);
+        transaction.addToBackStack("userDetails:" + person.login);
+        transaction.commitAllowingStateLoss();
+    }
+
+    public void openEditUserDetailsFragment(Fragment target, PersonData user) {
+        if (!isActive()) {
+            return;
+        }
+        EditUserDetailsFragment editUserDetailsFragment =
+                EditUserDetailsFragment.createFor(target, user);
+        editUserDetailsFragment.show(mFragmentManager, "editUser:" + user.login);
+    }
+}
diff --git a/samples-flatfoot/GithubBrowser/app/src/main/java/com/android/sample/githubbrowser/network/GithubNetworkManager.java b/samples-flatfoot/GithubBrowser/app/src/main/java/com/android/sample/githubbrowser/network/GithubNetworkManager.java
new file mode 100644
index 0000000..e95a5e8
--- /dev/null
+++ b/samples-flatfoot/GithubBrowser/app/src/main/java/com/android/sample/githubbrowser/network/GithubNetworkManager.java
@@ -0,0 +1,257 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES 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.sample.githubbrowser.network;
+
+import android.support.annotation.MainThread;
+import android.support.annotation.NonNull;
+
+import com.android.sample.githubbrowser.data.ContributorData;
+import com.android.sample.githubbrowser.data.PersonData;
+import com.android.sample.githubbrowser.data.RepositoryData;
+import com.android.sample.githubbrowser.model.AuthTokenModel;
+
+import java.io.IOException;
+import java.util.List;
+
+import javax.inject.Singleton;
+
+import okhttp3.HttpUrl;
+import okhttp3.Interceptor;
+import okhttp3.OkHttpClient;
+import okhttp3.Request;
+import retrofit2.Call;
+import retrofit2.Callback;
+import retrofit2.Response;
+import retrofit2.Retrofit;
+import retrofit2.converter.gson.GsonConverterFactory;
+
+/**
+ * This class is responsible for loading data from network.
+ */
+@Singleton
+public class GithubNetworkManager {
+    private GithubService mGithubService;
+    private final AuthTokenModel mAuthTokenModel;
+
+    /**
+     * Interface that exposes successful / failed calls to the rest of the application.
+     *
+     * @param <T> Payload data class.
+     */
+    public interface NetworkCallListener<T> {
+        /** Called when network response returned empty data, passing back the HTTP code. */
+        void onLoadEmpty(int httpCode);
+
+        /** Called when data has been succesfully loaded from the network. */
+        void onLoadSuccess(T data);
+
+        /** Called when data has failed loading from the network. */
+        void onLoadFailure();
+    }
+
+    /**
+     * Interface that exposes the option to cancel an existing network call.
+     */
+    public interface Cancelable {
+        /** Cancel the ongoing network call. */
+        void cancel();
+    }
+
+    private class CancelableCall implements Cancelable {
+        @NonNull private Call mCall;
+
+        private CancelableCall(@NonNull Call call) {
+            mCall = call;
+        }
+
+        @Override
+        public void cancel() {
+            mCall.cancel();
+        }
+    }
+
+    public GithubNetworkManager(AuthTokenModel authTokenModel) {
+        mAuthTokenModel = authTokenModel;
+        OkHttpClient.Builder httpClient = new OkHttpClient.Builder();
+        httpClient.addInterceptor(new Interceptor() {
+            @Override
+            public okhttp3.Response intercept(Chain chain) throws IOException {
+                Request original = chain.request();
+                HttpUrl originalHttpUrl = original.url();
+
+                HttpUrl url = originalHttpUrl.newBuilder()
+                        .addQueryParameter("access_token",
+                                mAuthTokenModel.getAuthTokenData().getValue())
+                        .build();
+                Request.Builder requestBuilder = original.newBuilder().url(url);
+
+                Request request = requestBuilder.build();
+                return chain.proceed(request);
+            }
+        });
+
+        httpClient.addInterceptor(new Interceptor() {
+            @Override
+            public okhttp3.Response intercept(Chain chain) throws IOException {
+                Request request = chain.request();
+                okhttp3.Response response = chain.proceed(request);
+                if (response.code() == 401 || response.code() == 403) {
+                    mAuthTokenModel.clearToken();
+                }
+                return response;
+            }
+        });
+
+
+        Retrofit retrofit = new Retrofit.Builder()
+                .baseUrl("https://api.github.com")
+                .addConverterFactory(GsonConverterFactory.create())
+                .client(httpClient.build())
+                .build();
+
+        mGithubService = retrofit.create(GithubService.class);
+    }
+
+    /**
+     * Fetches the specified page of repositories.
+     */
+    @MainThread
+    public CancelableCall listRepositories(String user, int pageIndex,
+            final NetworkCallListener<List<RepositoryData>> networkCallListener) {
+        Call<List<RepositoryData>> listRepositoriesCall = mGithubService.listRepositories(
+                user, pageIndex);
+        listRepositoriesCall.enqueue(new Callback<List<RepositoryData>>() {
+            @Override
+            public void onResponse(Call<List<RepositoryData>> call,
+                    Response<List<RepositoryData>> response) {
+                List<RepositoryData> body = response.body();
+                if (body == null) {
+                    networkCallListener.onLoadEmpty(response.code());
+                } else {
+                    networkCallListener.onLoadSuccess(body);
+                }
+            }
+
+            @Override
+            public void onFailure(Call<List<RepositoryData>> call, Throwable t) {
+                android.util.Log.e("GithubBrowser", "Call = " + call.toString(), t);
+                networkCallListener.onLoadFailure();
+            }
+        });
+        return new CancelableCall(listRepositoriesCall);
+    }
+
+    /**
+     * Fetches the details of the specified repository.
+     */
+    @MainThread
+    public CancelableCall getRepository(String user, String name,
+            final NetworkCallListener<RepositoryData> networkCallListener) {
+        Call<RepositoryData> getRepositoryCall = mGithubService.getRepository(user, name);
+        getRepositoryCall.enqueue(new Callback<RepositoryData>() {
+            @Override
+            public void onResponse(Call<RepositoryData> call,
+                    Response<RepositoryData> response) {
+                RepositoryData body = response.body();
+                if (body == null) {
+                    networkCallListener.onLoadEmpty(response.code());
+                } else {
+                    networkCallListener.onLoadSuccess(body);
+                }
+            }
+
+            @Override
+            public void onFailure(Call<RepositoryData> call, Throwable t) {
+                android.util.Log.e("GithubBrowser", "Call = " + call.toString(), t);
+                networkCallListener.onLoadFailure();
+            }
+        });
+        return new CancelableCall(getRepositoryCall);
+    }
+
+    /**
+     * Fetches the specified page of contributors.
+     */
+    @MainThread
+    public CancelableCall getContributors(String owner, String project, int page,
+            final NetworkCallListener<List<ContributorData>> networkCallListener) {
+        Call<List<ContributorData>> getContributorsCall = mGithubService.getContributors(
+                owner, project, page);
+        getContributorsCall.enqueue(new Callback<List<ContributorData>>() {
+            @Override
+            public void onResponse(Call<List<ContributorData>> call,
+                    Response<List<ContributorData>> response) {
+                List<ContributorData> body = response.body();
+                if (body == null) {
+                    networkCallListener.onLoadEmpty(response.code());
+                } else {
+                    networkCallListener.onLoadSuccess(body);
+                }
+            }
+
+            @Override
+            public void onFailure(Call<List<ContributorData>> call, Throwable t) {
+                android.util.Log.e("GithubBrowser", "Call = " + call.toString(), t);
+                networkCallListener.onLoadFailure();
+            }
+        });
+        return new CancelableCall(getContributorsCall);
+    }
+
+    /**
+     * Fetches the details of the specified user.
+     */
+    @MainThread
+    public CancelableCall getUser(String user,
+            final NetworkCallListener<PersonData> networkCallListener) {
+        Call<PersonData> getUserCall = mGithubService.getUser(user);
+        getUserCall.enqueue(new Callback<PersonData>() {
+            @Override
+            public void onResponse(Call<PersonData> call,
+                    Response<PersonData> response) {
+                PersonData body = response.body();
+                if (body == null) {
+                    networkCallListener.onLoadEmpty(response.code());
+                } else {
+                    networkCallListener.onLoadSuccess(body);
+                }
+            }
+
+            @Override
+            public void onFailure(Call<PersonData> call, Throwable t) {
+                android.util.Log.e("GithubBrowser", "Call = " + call.toString(), t);
+                networkCallListener.onLoadFailure();
+            }
+        });
+        return new CancelableCall(getUserCall);
+    }
+}
diff --git a/samples-flatfoot/GithubBrowser/app/src/main/java/com/android/sample/githubbrowser/network/GithubService.java b/samples-flatfoot/GithubBrowser/app/src/main/java/com/android/sample/githubbrowser/network/GithubService.java
new file mode 100644
index 0000000..c26c11e
--- /dev/null
+++ b/samples-flatfoot/GithubBrowser/app/src/main/java/com/android/sample/githubbrowser/network/GithubService.java
@@ -0,0 +1,73 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES 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.sample.githubbrowser.network;
+
+import com.android.sample.githubbrowser.data.ContributorData;
+import com.android.sample.githubbrowser.data.PersonData;
+import com.android.sample.githubbrowser.data.RepositoryData;
+
+import java.util.List;
+
+import retrofit2.Call;
+import retrofit2.http.GET;
+import retrofit2.http.Path;
+import retrofit2.http.Query;
+
+/**
+ * Retrofit-powered service to connect to Github backend.
+ */
+public interface GithubService {
+    /**
+     * Lists the repositories for the specified user.
+     */
+    @GET("/users/{user}/repos")
+    Call<List<RepositoryData>> listRepositories(@Path("user") String user, @Query("page") int page);
+
+    /**
+     * Gets the information about the specified repository.
+     */
+    @GET("/repos/{user}/{name}")
+    Call<RepositoryData> getRepository(@Path("user") String user, @Path("name") String name);
+
+    /**
+     * Lists the contributors for the specified project owned by the specified user.
+     */
+    @GET("/repos/{user}/{project}/contributors")
+    Call<List<ContributorData>> getContributors(@Path("user") String owner,
+            @Path("project") String project, @Query("page") int page);
+
+    /**
+     * Gets the information about the specified user.
+     */
+    @GET("/users/{user}")
+    Call<PersonData> getUser(@Path("user") String user);
+}
diff --git a/samples-flatfoot/GithubBrowser/app/src/main/java/com/android/sample/githubbrowser/util/ChainedLiveData.java b/samples-flatfoot/GithubBrowser/app/src/main/java/com/android/sample/githubbrowser/util/ChainedLiveData.java
new file mode 100644
index 0000000..edba758
--- /dev/null
+++ b/samples-flatfoot/GithubBrowser/app/src/main/java/com/android/sample/githubbrowser/util/ChainedLiveData.java
@@ -0,0 +1,73 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES 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.sample.githubbrowser.util;
+
+import android.support.annotation.Nullable;
+
+import com.android.support.lifecycle.LiveData;
+import com.android.support.lifecycle.Observer;
+
+/**
+ * A live data that can be backed by another live data and this backing live data can be swapped.
+ * It automatically starts / stops observing on the backing live data as this LiveData's observers
+ * and active state changes.
+ * <p>
+ * This is useful when we want to use a LiveData in a model that arrives from another provider. We
+ * don't want the UI to care about this nor we want to leak previous LiveData instances.
+ * @param <T>
+ */
+public class ChainedLiveData<T> extends LiveData<T> {
+    private final Observer<T> mObserver = new Observer<T>() {
+        @Override
+        public void onChanged(@Nullable T t) {
+            setValue(t);
+        }
+    };
+
+    @Nullable
+    private LiveData<T> mBackingLiveData;
+
+    public void setBackingLiveData(@Nullable LiveData<T> backingLiveData) {
+        if (mBackingLiveData != null) {
+            mBackingLiveData.removeObserver(mObserver);
+        }
+        mBackingLiveData = backingLiveData;
+        if (backingLiveData == null) {
+            setValue(null);
+        } else {
+            if (getActiveObserverCount() > 0) {
+                backingLiveData.observeForever(mObserver);
+            } else {
+                setValue(backingLiveData.getValue());
+            }
+        }
+    }
+
+    @Override
+    protected void onActive() {
+        if (mBackingLiveData != null) {
+            mBackingLiveData.observeForever(mObserver);
+        }
+    }
+
+    @Override
+    protected void onInactive() {
+        if (mBackingLiveData != null) {
+            mBackingLiveData.removeObserver(mObserver);
+        }
+    }
+}
diff --git a/samples-flatfoot/GithubBrowser/app/src/main/java/com/android/sample/githubbrowser/view/PersonClickCallback.java b/samples-flatfoot/GithubBrowser/app/src/main/java/com/android/sample/githubbrowser/view/PersonClickCallback.java
new file mode 100644
index 0000000..189c6cc
--- /dev/null
+++ b/samples-flatfoot/GithubBrowser/app/src/main/java/com/android/sample/githubbrowser/view/PersonClickCallback.java
@@ -0,0 +1,23 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES 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.sample.githubbrowser.view;
+
+import com.android.sample.githubbrowser.data.PersonData;
+
+public interface PersonClickCallback {
+    void onClick(PersonData user);
+}
diff --git a/samples-flatfoot/GithubBrowser/app/src/main/java/com/android/sample/githubbrowser/view/RepoClickCallback.java b/samples-flatfoot/GithubBrowser/app/src/main/java/com/android/sample/githubbrowser/view/RepoClickCallback.java
new file mode 100644
index 0000000..4f6658b
--- /dev/null
+++ b/samples-flatfoot/GithubBrowser/app/src/main/java/com/android/sample/githubbrowser/view/RepoClickCallback.java
@@ -0,0 +1,23 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES 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.sample.githubbrowser.view;
+
+import com.android.sample.githubbrowser.data.RepositoryData;
+
+public interface RepoClickCallback {
+    void onClick(RepositoryData repositoryData);
+}
diff --git a/samples-flatfoot/GithubBrowser/app/src/main/java/com/android/sample/githubbrowser/viewmodel/ContributorListModel.java b/samples-flatfoot/GithubBrowser/app/src/main/java/com/android/sample/githubbrowser/viewmodel/ContributorListModel.java
new file mode 100644
index 0000000..7facc31
--- /dev/null
+++ b/samples-flatfoot/GithubBrowser/app/src/main/java/com/android/sample/githubbrowser/viewmodel/ContributorListModel.java
@@ -0,0 +1,252 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES 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.sample.githubbrowser.viewmodel;
+
+import android.os.AsyncTask;
+import android.support.annotation.MainThread;
+import android.support.annotation.WorkerThread;
+
+import com.android.sample.githubbrowser.data.ContributorData;
+import com.android.sample.githubbrowser.data.ContributorSearchData;
+import com.android.sample.githubbrowser.data.SearchQueryData;
+import com.android.sample.githubbrowser.db.GithubDao;
+import com.android.sample.githubbrowser.db.GithubDatabase;
+import com.android.sample.githubbrowser.di.AppComponent;
+import com.android.sample.githubbrowser.network.GithubNetworkManager;
+import com.android.sample.githubbrowser.util.ChainedLiveData;
+import com.android.support.lifecycle.LiveData;
+
+import java.util.List;
+import java.util.concurrent.atomic.AtomicBoolean;
+import java.util.concurrent.atomic.AtomicInteger;
+
+import javax.inject.Inject;
+
+/**
+ * View model for contributor list data.
+ */
+public class ContributorListModel extends InjectableViewModel {
+    private String mOwner;
+    private String mProject;
+
+    private final ChainedLiveData<List<ContributorData>> mContributorListLiveData
+            = new ChainedLiveData<>();
+    private AtomicBoolean mHasNetworkRequestPending = new AtomicBoolean(false);
+    private GithubNetworkManager.Cancelable mCurrentNetworkCall;
+
+    private SearchQueryData mSearchQueryData;
+    private AtomicInteger mLastRequestedIndex = new AtomicInteger(0);
+
+    @Inject
+    GithubNetworkManager mGithubNetworkManager;
+    @Inject
+    GithubDatabase mDatabase;
+
+    @Override
+    void inject(AppComponent appComponent) {
+        appComponent.inject(this);
+    }
+
+    /**
+     * Sets new search terms.
+     */
+    @MainThread
+    public void setSearchTerms(String owner, String project) {
+        mOwner = owner;
+        mProject = project;
+
+        if (mCurrentNetworkCall != null) {
+            mCurrentNetworkCall.cancel();
+        }
+        if (mOwner == null || mProject == null) {
+            mContributorListLiveData.setBackingLiveData(null);
+            return;
+        }
+
+        final GithubDao githubDao = mDatabase.getGithubDao();
+
+        // Get the LiveData wrapper around the list of contributors that match our current
+        // search query. The wrapped list will be updated on every successful network request
+        // that is performed for data that is not available in our database.
+        mContributorListLiveData
+                .setBackingLiveData(githubDao.getContributors(mOwner + "/" + mProject));
+
+        mHasNetworkRequestPending.set(false);
+
+        new AsyncTask<String, Void, Void>() {
+            @Override
+            protected Void doInBackground(String... params) {
+                // Get data about locally persisted results of our current search query. Note that
+                // since this is working with a disk-based database, we're running off the main
+                // thread.
+                mSearchQueryData = githubDao.getSearchQueryData(
+                        params[0], SearchQueryData.REPOSITORY_CONTRIBUTORS);
+                if (mSearchQueryData == null) {
+                    // This query has not been performed before - initialize an entry in the
+                    // database. TODO - consult the timestamp of network requests for staleness.
+                    mSearchQueryData = new SearchQueryData();
+                    mSearchQueryData.searchQuery = params[0];
+                    mSearchQueryData.searchKind = SearchQueryData.REPOSITORY_CONTRIBUTORS;
+                    mSearchQueryData.numberOfFetchedItems = -1;
+                    githubDao.update(mSearchQueryData);
+                }
+                return null;
+            }
+
+            @Override
+            protected void onPostExecute(Void aVoid) {
+                fetchNextPage();
+            }
+        }.execute(mOwner + "/" + mProject);
+    }
+
+    private void fetchNextPage() {
+        if (mSearchQueryData == null) {
+            // Not ready to fetch yet.
+            return;
+        }
+
+        // Do we have data in the database?
+        if (mSearchQueryData.numberOfFetchedItems >= mLastRequestedIndex.get()) {
+            // We already have the data stored (and retrieved) from database.
+            return;
+        }
+
+        if (mHasNetworkRequestPending.get() || mSearchQueryData.hasNoMoreData) {
+            // Previous request still processing or no more results
+            return;
+        }
+
+        mHasNetworkRequestPending.set(true);
+        mCurrentNetworkCall = mGithubNetworkManager.getContributors(
+                mOwner, mProject, mSearchQueryData.indexOfLastFetchedPage + 1,
+                new GithubNetworkManager.NetworkCallListener<List<ContributorData>>() {
+                    @Override
+                    public void onLoadEmpty(int httpCode) {
+                    }
+
+                    @Override
+                    public void onLoadSuccess(List<ContributorData> data) {
+                        new AsyncTask<ContributorData, Void, Void>() {
+                            @Override
+                            protected Void doInBackground(ContributorData... params) {
+                                // Note that since we're going to be inserting data into disk-based
+                                // database, we need to be running off the main thread.
+                                processNewPageOfData(params);
+                                return null;
+                            }
+                        }.execute(data.toArray(new ContributorData[data.size()]));
+                    }
+
+                    @Override
+                    public void onLoadFailure() {
+                    }
+                });
+    }
+
+    @WorkerThread
+    private void processNewPageOfData(ContributorData... data) {
+        try {
+            mDatabase.beginTransaction();
+            int newDataCount = data.length;
+
+            final GithubDao githubDao = mDatabase.getGithubDao();
+            final int indexOfFirstData = mSearchQueryData.numberOfFetchedItems;
+            // Update the metadata about our current search query (in the database)
+            if (newDataCount == 0) {
+                mSearchQueryData.hasNoMoreData = true;
+            } else {
+                if (mSearchQueryData.indexOfLastFetchedPage == 0) {
+                    mSearchQueryData.timestamp = System.currentTimeMillis();
+                }
+                mSearchQueryData.indexOfLastFetchedPage++;
+                mSearchQueryData.numberOfFetchedItems += newDataCount;
+            }
+            githubDao.update(mSearchQueryData);
+
+            if (newDataCount > 0) {
+                // Insert entries for the newly loaded contributors in two places:
+                // 1. The table that stores contributor IDs that match a specific query.
+                // 2. The table that stores full data on each individual contributor.
+                // This way we don't store multiple full entries for the same contributor
+                // that happens to match two or more search queries.
+                ContributorSearchData[] contributorSearchDataArray =
+                        new ContributorSearchData[newDataCount];
+                for (int i = 0; i < newDataCount; i++) {
+                    contributorSearchDataArray[i] = new ContributorSearchData();
+                    contributorSearchDataArray[i].searchQuery = mOwner + "/" + mProject;
+                    contributorSearchDataArray[i].resultIndex = indexOfFirstData + i;
+                    contributorSearchDataArray[i].contributorId = data[i].id;
+                    contributorSearchDataArray[i].contributions = data[i].contributions;
+                }
+                githubDao.insert(contributorSearchDataArray);
+                githubDao.insert(data);
+            }
+            mDatabase.setTransactionSuccessful();
+        } finally {
+            mDatabase.endTransaction();
+        }
+
+        mHasNetworkRequestPending.set(false);
+    }
+
+    /**
+     * Fetches data at specified index if data does not exist yet.
+     */
+    public void fetchAtIndexIfNecessary(int index) {
+        if (mSearchQueryData == null) {
+            // If we're here, we've been asked to start fetching items before we've retrieved
+            // the top-level metadata for our search. Save the requested index and return. Once
+            // that metadata is fetched off the main thread in the AsyncTask executed in
+            // setSearchTerms, we'll call fetchNextPage().
+            mLastRequestedIndex.set(index);
+            return;
+        }
+
+        if (mHasNetworkRequestPending.get() || mSearchQueryData.hasNoMoreData) {
+            // Previous request still processing or no more results
+            return;
+        }
+
+        mLastRequestedIndex.set(index);
+
+        fetchNextPage();
+    }
+
+    /**
+     * Returns the {@link LiveData} object that wraps the current list of contributors that matches
+     * the last set search terms.
+     */
+    public LiveData<List<ContributorData>> getContributorListLiveData() {
+        return mContributorListLiveData;
+    }
+}
diff --git a/samples-flatfoot/GithubBrowser/app/src/main/java/com/android/sample/githubbrowser/viewmodel/InjectableViewModel.java b/samples-flatfoot/GithubBrowser/app/src/main/java/com/android/sample/githubbrowser/viewmodel/InjectableViewModel.java
new file mode 100644
index 0000000..1598448
--- /dev/null
+++ b/samples-flatfoot/GithubBrowser/app/src/main/java/com/android/sample/githubbrowser/viewmodel/InjectableViewModel.java
@@ -0,0 +1,29 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES 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.sample.githubbrowser.viewmodel;
+
+import com.android.sample.githubbrowser.GithubBrowserApp;
+import com.android.sample.githubbrowser.di.AppComponent;
+import com.android.support.lifecycle.ViewModel;
+
+public abstract class InjectableViewModel extends ViewModel {
+    public InjectableViewModel() {
+        inject(((GithubBrowserApp) getApplication()).getAppComponent());
+    }
+
+    abstract void inject(AppComponent appComponent);
+}
diff --git a/samples-flatfoot/GithubBrowser/app/src/main/java/com/android/sample/githubbrowser/viewmodel/PersonDataModel.java b/samples-flatfoot/GithubBrowser/app/src/main/java/com/android/sample/githubbrowser/viewmodel/PersonDataModel.java
new file mode 100644
index 0000000..fda04f2
--- /dev/null
+++ b/samples-flatfoot/GithubBrowser/app/src/main/java/com/android/sample/githubbrowser/viewmodel/PersonDataModel.java
@@ -0,0 +1,150 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES 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.sample.githubbrowser.viewmodel;
+
+import android.os.AsyncTask;
+import android.support.annotation.MainThread;
+
+import com.android.sample.githubbrowser.data.PersonData;
+import com.android.sample.githubbrowser.db.GithubDatabase;
+import com.android.sample.githubbrowser.di.AppComponent;
+import com.android.sample.githubbrowser.network.GithubNetworkManager;
+import com.android.sample.githubbrowser.network.GithubNetworkManager.NetworkCallListener;
+import com.android.sample.githubbrowser.util.ChainedLiveData;
+import com.android.support.lifecycle.LiveData;
+
+import java.util.concurrent.atomic.AtomicBoolean;
+
+import javax.inject.Inject;
+
+/**
+ * View model for the full person data.
+ */
+public class PersonDataModel extends InjectableViewModel {
+    private AtomicBoolean mHasNetworkRequestPending = new AtomicBoolean(false);
+    private final ChainedLiveData<PersonData> mPersonData = new ChainedLiveData<>();
+
+    @Inject
+    GithubNetworkManager mGithubNetworkManager;
+    @Inject
+    GithubDatabase mDatabase;
+
+    @Override
+    public void inject(AppComponent appComponent) {
+        appComponent.inject(this);
+    }
+
+    /**
+     * Returns the {@LiveData} object that wraps the full person data.
+     */
+    public LiveData<PersonData> getPersonData() {
+        return mPersonData;
+    }
+
+    /**
+     * Sets the login for fetching the full person data.
+     */
+    @MainThread
+    public synchronized void loadData(final String login, final boolean forceFullLoad) {
+        // Note that the usage of this view model class guarantees that we're always calling
+        // with the same login. So checking the value of fetching field is enough to prevent
+        // multiple concurrent remote / local DB fetches.
+        boolean isFetching = mHasNetworkRequestPending.get();
+        boolean havePersonDataAlready = mPersonData.getValue() != null
+                && (!forceFullLoad || mPersonData.getValue().isFullData());
+        if (isFetching || havePersonDataAlready) {
+            // We are either fetching the data or have the data already
+            return;
+        }
+        mPersonData.setBackingLiveData(mDatabase.getGithubDao().loadPerson(login));
+        if (mPersonData.getValue() == null || forceFullLoad) {
+            // Issue the network request to bring in the data
+            mHasNetworkRequestPending.set(true);
+
+            mGithubNetworkManager.getUser(login,
+                    new NetworkCallListener<PersonData>() {
+                        @Override
+                        public void onLoadEmpty(int httpCode) {
+                            mHasNetworkRequestPending.set(false);
+                        }
+
+                        @Override
+                        public void onLoadSuccess(PersonData data) {
+                            onDataLoadedFromNetwork(data);
+                        }
+
+                        @Override
+                        public void onLoadFailure() {
+                            mHasNetworkRequestPending.set(false);
+                        }
+                    });
+        }
+    }
+
+    @MainThread
+    private void onDataLoadedFromNetwork(PersonData data) {
+        mHasNetworkRequestPending.set(false);
+
+        // Wrap a DB insert call with another AsyncTask. Otherwise we'd
+        // be doing a disk IO operation on the UI thread.
+        new AsyncTask<PersonData, Void, Void>() {
+            @Override
+            protected Void doInBackground(PersonData... params) {
+                mDatabase.getGithubDao().insertOrReplacePerson(params[0]);
+                return null;
+            }
+        }.execute(data);
+    }
+
+    /**
+     * Updates the data wrapped by this model.
+     */
+    @MainThread
+    public void update(final String login, final String email, final String location) {
+        // Create a copy of the currently wrapped data
+        // Update the relevant fields
+        // And update the entry for this person in our database so that it's reflected
+        // in the UI the next time it's fetched and displayed
+        // Wrap a DB update call with an AsyncTask. Otherwise we'd be doing a disk IO operation on
+        // the UI thread.
+        new AsyncTask<Void, Void, Void>() {
+            @Override
+            protected Void doInBackground(Void... params) {
+                mDatabase.getGithubDao().updateUser(login, email, location);
+                return null;
+            }
+        }.execute();
+
+        // Note - this is where you would also issue a network request to update user data
+        // on the remote backend.
+    }
+}
diff --git a/samples-flatfoot/GithubBrowser/app/src/main/java/com/android/sample/githubbrowser/viewmodel/RepositoryDataModel.java b/samples-flatfoot/GithubBrowser/app/src/main/java/com/android/sample/githubbrowser/viewmodel/RepositoryDataModel.java
new file mode 100644
index 0000000..8f659ad
--- /dev/null
+++ b/samples-flatfoot/GithubBrowser/app/src/main/java/com/android/sample/githubbrowser/viewmodel/RepositoryDataModel.java
@@ -0,0 +1,130 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES 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.sample.githubbrowser.viewmodel;
+
+import android.os.AsyncTask;
+import android.support.annotation.MainThread;
+
+import com.android.sample.githubbrowser.data.RepositoryData;
+import com.android.sample.githubbrowser.db.GithubDatabase;
+import com.android.sample.githubbrowser.di.AppComponent;
+import com.android.sample.githubbrowser.network.GithubNetworkManager;
+import com.android.sample.githubbrowser.util.ChainedLiveData;
+import com.android.support.lifecycle.LiveData;
+
+import java.util.concurrent.atomic.AtomicBoolean;
+
+import javax.inject.Inject;
+
+/**
+ * View model for the full repository data.
+ */
+public class RepositoryDataModel extends InjectableViewModel {
+    private AtomicBoolean mHasNetworkRequestPending = new AtomicBoolean(false);
+    private final ChainedLiveData<RepositoryData> mRepositoryData = new ChainedLiveData<>();
+    @Inject
+    GithubNetworkManager mGithubNetworkManager;
+    @Inject
+    GithubDatabase mDatabase;
+
+    @Override
+    void inject(AppComponent appComponent) {
+        appComponent.inject(this);
+    }
+
+    /**
+     * Returns the {@link LiveData} object that wraps the full repository data.
+     */
+    public LiveData<RepositoryData> getRepositoryData() {
+        return mRepositoryData;
+    }
+
+    /**
+     * Sets the information for fetching the full repository data.
+     */
+    @MainThread
+    public synchronized void loadData(final String id, final String fullName) {
+        // Note that the usage of this view model class guarantees that we're always calling
+        // with the same info. So checking the value of fetching field is enough to prevent
+        // multiple concurrent remote / local DB fetches.
+        boolean isFetching = mHasNetworkRequestPending.get();
+        boolean haveRepoDataAlready = (mRepositoryData != null)
+                && (mRepositoryData.getValue() != null);
+        if (isFetching || haveRepoDataAlready) {
+            // We are either fetching the data or have the data already
+            return;
+        }
+
+        mRepositoryData.setBackingLiveData(mDatabase.getGithubDao().loadRepository(id));
+        if (mRepositoryData.getValue() == null) {
+            // Issue the network request to bring in the data
+            mHasNetworkRequestPending.set(true);
+
+            // TODO - this is temporary until Room persists non-primitive fields. Until
+            // then we split full name into user and name manually
+            String[] split = fullName.split("/");
+            mGithubNetworkManager.getRepository(split[0], split[1],
+                    new GithubNetworkManager.NetworkCallListener<RepositoryData>() {
+                        @Override
+                        public void onLoadEmpty(int httpCode) {
+                            mHasNetworkRequestPending.set(false);
+                        }
+
+                        @Override
+                        public void onLoadSuccess(RepositoryData data) {
+                            onDataLoadedFromNetwork(data, mDatabase);
+                        }
+
+                        @Override
+                        public void onLoadFailure() {
+                            mHasNetworkRequestPending.set(false);
+                        }
+                    });
+        }
+    }
+
+    @MainThread
+    private void onDataLoadedFromNetwork(RepositoryData data, final GithubDatabase db) {
+        mRepositoryData.setValue(data);
+        mHasNetworkRequestPending.set(false);
+
+        // Wrap a DB insert call with another AsyncTask. Otherwise we'd
+        // be doing a disk IO operation on the UI thread.
+        new AsyncTask<RepositoryData, Void, Void>() {
+            @Override
+            protected Void doInBackground(RepositoryData... params) {
+                db.getGithubDao().insertOrReplaceRepository(params[0]);
+                return null;
+            }
+        }.execute(data);
+    }
+}
diff --git a/samples-flatfoot/GithubBrowser/app/src/main/java/com/android/sample/githubbrowser/viewmodel/RepositoryListModel.java b/samples-flatfoot/GithubBrowser/app/src/main/java/com/android/sample/githubbrowser/viewmodel/RepositoryListModel.java
new file mode 100644
index 0000000..6a158df
--- /dev/null
+++ b/samples-flatfoot/GithubBrowser/app/src/main/java/com/android/sample/githubbrowser/viewmodel/RepositoryListModel.java
@@ -0,0 +1,302 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES 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.sample.githubbrowser.viewmodel;
+
+import android.os.AsyncTask;
+import android.support.annotation.MainThread;
+import android.support.annotation.WorkerThread;
+import android.text.TextUtils;
+
+import com.android.sample.githubbrowser.data.GeneralRepoSearchData;
+import com.android.sample.githubbrowser.data.RepositoryData;
+import com.android.sample.githubbrowser.data.SearchQueryData;
+import com.android.sample.githubbrowser.db.GithubDao;
+import com.android.sample.githubbrowser.db.GithubDatabase;
+import com.android.sample.githubbrowser.di.AppComponent;
+import com.android.sample.githubbrowser.model.AuthTokenModel;
+import com.android.sample.githubbrowser.network.GithubNetworkManager;
+import com.android.sample.githubbrowser.util.ChainedLiveData;
+import com.android.sample.githubbrowser.viewmodel.InjectableViewModel;
+import com.android.support.lifecycle.LiveData;
+
+import java.util.List;
+import java.util.concurrent.atomic.AtomicBoolean;
+import java.util.concurrent.atomic.AtomicInteger;
+
+import javax.inject.Inject;
+
+/**
+ * View model for repository list data.
+ */
+public class RepositoryListModel extends InjectableViewModel {
+    /** Constant for the initial loading state. */
+    public static final int STATE_INITIAL_LOADING = 0;
+    /** Constant for the empty / no data state. */
+    public static final int STATE_EMPTY = 1;
+    /** Constant for the data state. */
+    public static final int STATE_DATA = 2;
+    /** Constant for the error state. */
+    public static final int STATE_ERROR = 3;
+
+    private String mSearchTerm;
+
+    private final ChainedLiveData<List<RepositoryData>> mRepositoryListLiveData
+            = new ChainedLiveData<>();
+    private final LiveData<Integer> mStateLiveData = new LiveData<>();
+    private AtomicBoolean mHasNetworkRequestPending = new AtomicBoolean(false);
+    private GithubNetworkManager.Cancelable mCurrentNetworkCall;
+
+    private SearchQueryData mSearchQueryData;
+    private AtomicInteger mLastRequestedIndex = new AtomicInteger(0);
+
+    @Inject
+    GithubNetworkManager mGithubNetworkManager;
+    @Inject
+    GithubDatabase mDatabase;
+    @Inject
+    AuthTokenModel mAuthTokenModel;
+
+    @Override
+    void inject(AppComponent appComponent) {
+        appComponent.inject(this);
+    }
+
+    /**
+     * Returns true if the current search term is not empty.
+     */
+    public boolean hasSearchTerm() {
+        return !TextUtils.isEmpty(mSearchTerm);
+    }
+
+    /**
+     * Sets new search term.
+     */
+    @MainThread
+    public void setSearchTerm(String searchTerm) {
+        mSearchTerm = searchTerm;
+
+        if (mCurrentNetworkCall != null) {
+            mCurrentNetworkCall.cancel();
+        }
+
+        final GithubDao githubDao = mDatabase.getGithubDao();
+
+        // Get the LiveData wrapper around the list of repositories that match our current
+        // search query. The wrapped list will be updated on every successful network request
+        // that is performed for data that is not available in our database.
+        mRepositoryListLiveData.setBackingLiveData(githubDao.getRepositories(mSearchTerm));
+
+        mStateLiveData.setValue(STATE_INITIAL_LOADING);
+        mHasNetworkRequestPending.set(false);
+
+        new AsyncTask<String, Void, Void>() {
+            @Override
+            protected Void doInBackground(String... params) {
+                // Get data about locally persisted results of our current search query. Note that
+                // since this is working with a disk-based database, we're running off the main
+                // thread.
+                mSearchQueryData = githubDao.getSearchQueryData(
+                        params[0], SearchQueryData.GENERAL_REPOSITORIES);
+                if (mSearchQueryData == null) {
+                    // This query has not been performed before - initialize an entry in the
+                    // database. TODO - consult the timestamp of network requests for staleness.
+                    mSearchQueryData = new SearchQueryData();
+                    mSearchQueryData.searchQuery = params[0];
+                    mSearchQueryData.searchKind = SearchQueryData.GENERAL_REPOSITORIES;
+                    mSearchQueryData.numberOfFetchedItems = -1;
+                    githubDao.update(mSearchQueryData);
+                }
+                return null;
+            }
+
+            @Override
+            protected void onPostExecute(Void aVoid) {
+                fetchNextPage();
+            }
+        }.execute(mSearchTerm);
+    }
+
+    private void fetchNextPage() {
+        if (mSearchQueryData == null) {
+            // Not ready to fetch yet.
+            return;
+        }
+
+        // Do we have data in the database?
+        if (mSearchQueryData.numberOfFetchedItems >= mLastRequestedIndex.get()) {
+            // We already have the data stored (and retrieved) from database.
+            mStateLiveData.setValue(STATE_DATA);
+            return;
+        }
+
+        if (mHasNetworkRequestPending.get()) {
+            // Previous request still processing
+            return;
+        }
+
+        if (mSearchQueryData.hasNoMoreData) {
+            // We don't have any more results
+            if (mSearchQueryData.numberOfFetchedItems <= 0) {
+                mStateLiveData.setValue(STATE_EMPTY);
+            }
+            return;
+        }
+
+        mHasNetworkRequestPending.set(true);
+        mCurrentNetworkCall = mGithubNetworkManager.listRepositories(
+                mSearchTerm, mSearchQueryData.indexOfLastFetchedPage + 1,
+                new GithubNetworkManager.NetworkCallListener<List<RepositoryData>>() {
+                    @Override
+                    public void onLoadEmpty(int httpCode) {
+                        switch (httpCode) {
+                            case 404:
+                                // No such user
+                                mStateLiveData.setValue(STATE_EMPTY);
+                                break;
+                            default:
+                                mStateLiveData.setValue(STATE_ERROR);
+                        }
+                    }
+
+                    @Override
+                    public void onLoadSuccess(List<RepositoryData> data) {
+                        new AsyncTask<RepositoryData, Void, Void>() {
+                            @Override
+                            protected Void doInBackground(RepositoryData... params) {
+                                // Note that since we're going to be inserting data into disk-based
+                                // database, we need to be running off the main thread.
+                                processNewPageOfData(params);
+                                return null;
+                            }
+                        }.execute(data.toArray(new RepositoryData[data.size()]));
+                    }
+
+                    @Override
+                    public void onLoadFailure() {
+                        mStateLiveData.setValue(STATE_ERROR);
+                    }
+                });
+    }
+
+    @WorkerThread
+    private void processNewPageOfData(RepositoryData... data) {
+        try {
+            mDatabase.beginTransaction();
+            int newDataCount = data.length;
+
+            final GithubDao githubDao = mDatabase.getGithubDao();
+            final int indexOfFirstData = mSearchQueryData.numberOfFetchedItems;
+            // Update the metadata about our current search query (in the database)
+            if (newDataCount == 0) {
+                mSearchQueryData.hasNoMoreData = true;
+            } else {
+                if (mSearchQueryData.indexOfLastFetchedPage == 0) {
+                    mSearchQueryData.timestamp = System.currentTimeMillis();
+                }
+                mSearchQueryData.indexOfLastFetchedPage++;
+                mSearchQueryData.numberOfFetchedItems += newDataCount;
+            }
+            githubDao.update(mSearchQueryData);
+
+            if (newDataCount > 0) {
+                // Insert entries for the newly loaded repositories in two places:
+                // 1. The table that stores repository IDs that match a specific query.
+                // 2. The table that stores full data on each individual repository.
+                // This way we don't store multiple full entries for the same repository
+                // that happens to match two or more search queries.
+                GeneralRepoSearchData[] generalRepoSearchDataArray =
+                        new GeneralRepoSearchData[newDataCount];
+                for (int i = 0; i < newDataCount; i++) {
+                    generalRepoSearchDataArray[i] = new GeneralRepoSearchData();
+                    generalRepoSearchDataArray[i].searchQuery = mSearchTerm;
+                    generalRepoSearchDataArray[i].resultIndex = indexOfFirstData + i;
+                    generalRepoSearchDataArray[i].repoId = data[i].id;
+                }
+                githubDao.insert(generalRepoSearchDataArray);
+                githubDao.insert(data);
+            }
+            mDatabase.setTransactionSuccessful();
+        } finally {
+            mDatabase.endTransaction();
+        }
+
+        mHasNetworkRequestPending.set(false);
+        mStateLiveData.postValue(
+                (mSearchQueryData.numberOfFetchedItems <= 0) && mSearchQueryData.hasNoMoreData
+                    ? STATE_EMPTY : STATE_DATA);
+    }
+
+    /**
+     * Fetches data at specified index if data does not exist yet.
+     */
+    public void fetchAtIndexIfNecessary(int index) {
+        if (mSearchQueryData == null) {
+            // If we're here, we've been asked to start fetching items before we've retrieved
+            // the top-level metadata for our search. Save the requested index and return. Once
+            // that metadata is fetched off the main thread in the AsyncTask executed in
+            // setSearchTerms, we'll call fetchNextPage().
+            mLastRequestedIndex.set(index);
+            return;
+        }
+
+        if (mHasNetworkRequestPending.get() || mSearchQueryData.hasNoMoreData) {
+            // Previous request still processing or no more results
+            return;
+        }
+
+        mLastRequestedIndex.set(index);
+
+        fetchNextPage();
+    }
+
+    /**
+     * Resumes loading of data in this model.
+     */
+    public void resumeLoading() {
+        fetchNextPage();
+    }
+
+    /**
+     * Returns the {@LiveData} object that wraps the current list of repos that matches the last
+     * set search term.
+     */
+    public LiveData<List<RepositoryData>> getRepositoryListLiveData() {
+        return mRepositoryListLiveData;
+    }
+
+    /**
+     * Returns the {@LiveData} object that wraps the current data state.
+     */
+    public LiveData<Integer> getStateLiveData() {
+        return mStateLiveData;
+    }
+}
diff --git a/samples-flatfoot/GithubBrowser/app/src/main/java/com/android/sample/githubbrowser/viewmodel/RepositorySearchModel.java b/samples-flatfoot/GithubBrowser/app/src/main/java/com/android/sample/githubbrowser/viewmodel/RepositorySearchModel.java
new file mode 100644
index 0000000..7dd9ad9
--- /dev/null
+++ b/samples-flatfoot/GithubBrowser/app/src/main/java/com/android/sample/githubbrowser/viewmodel/RepositorySearchModel.java
@@ -0,0 +1,63 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES 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.sample.githubbrowser.viewmodel;
+
+import android.text.TextUtils;
+
+import com.android.support.lifecycle.LiveData;
+import com.android.support.lifecycle.ViewModel;
+
+/**
+ * Model for the top-level search.
+ */
+public class RepositorySearchModel extends ViewModel {
+    private LiveData<String> mSearchQuery = new LiveData<>();
+
+    /**
+     * Sets new search query. The second parameter should be used to specify whether
+     * the currently set query should be overwritten.
+     */
+    public void setQuery(String query, boolean ignoreIfAlreadySet) {
+        if (ignoreIfAlreadySet && !TextUtils.isEmpty(mSearchQuery.getValue())) {
+            return;
+        }
+
+        mSearchQuery.setValue(query);
+    }
+
+    /**
+     * Returns the {@LiveData} object that wraps the top-level search query.
+     */
+    public LiveData<String> getSearchQueryData() {
+        return mSearchQuery;
+    }
+}
diff --git a/samples-flatfoot/GithubBrowser/app/src/main/res/drawable-hdpi/ic_bug_report_black_18dp.png b/samples-flatfoot/GithubBrowser/app/src/main/res/drawable-hdpi/ic_bug_report_black_18dp.png
new file mode 100644
index 0000000..9eff049
--- /dev/null
+++ b/samples-flatfoot/GithubBrowser/app/src/main/res/drawable-hdpi/ic_bug_report_black_18dp.png
Binary files differ
diff --git a/samples-flatfoot/GithubBrowser/app/src/main/res/drawable-hdpi/ic_content_copy_black_18dp.png b/samples-flatfoot/GithubBrowser/app/src/main/res/drawable-hdpi/ic_content_copy_black_18dp.png
new file mode 100644
index 0000000..91b3acf
--- /dev/null
+++ b/samples-flatfoot/GithubBrowser/app/src/main/res/drawable-hdpi/ic_content_copy_black_18dp.png
Binary files differ
diff --git a/samples-flatfoot/GithubBrowser/app/src/main/res/drawable-hdpi/ic_email_black_18dp.png b/samples-flatfoot/GithubBrowser/app/src/main/res/drawable-hdpi/ic_email_black_18dp.png
new file mode 100644
index 0000000..ef03f95
--- /dev/null
+++ b/samples-flatfoot/GithubBrowser/app/src/main/res/drawable-hdpi/ic_email_black_18dp.png
Binary files differ
diff --git a/samples-flatfoot/GithubBrowser/app/src/main/res/drawable-hdpi/ic_link_black_18dp.png b/samples-flatfoot/GithubBrowser/app/src/main/res/drawable-hdpi/ic_link_black_18dp.png
new file mode 100644
index 0000000..788c578
--- /dev/null
+++ b/samples-flatfoot/GithubBrowser/app/src/main/res/drawable-hdpi/ic_link_black_18dp.png
Binary files differ
diff --git a/samples-flatfoot/GithubBrowser/app/src/main/res/drawable-hdpi/ic_location_on_black_18dp.png b/samples-flatfoot/GithubBrowser/app/src/main/res/drawable-hdpi/ic_location_on_black_18dp.png
new file mode 100644
index 0000000..5e45f7e
--- /dev/null
+++ b/samples-flatfoot/GithubBrowser/app/src/main/res/drawable-hdpi/ic_location_on_black_18dp.png
Binary files differ
diff --git a/samples-flatfoot/GithubBrowser/app/src/main/res/drawable-hdpi/ic_mode_edit_black_24dp.png b/samples-flatfoot/GithubBrowser/app/src/main/res/drawable-hdpi/ic_mode_edit_black_24dp.png
new file mode 100644
index 0000000..e531d72
--- /dev/null
+++ b/samples-flatfoot/GithubBrowser/app/src/main/res/drawable-hdpi/ic_mode_edit_black_24dp.png
Binary files differ
diff --git a/samples-flatfoot/GithubBrowser/app/src/main/res/drawable-hdpi/ic_people_black_18dp.png b/samples-flatfoot/GithubBrowser/app/src/main/res/drawable-hdpi/ic_people_black_18dp.png
new file mode 100644
index 0000000..f6ac436
--- /dev/null
+++ b/samples-flatfoot/GithubBrowser/app/src/main/res/drawable-hdpi/ic_people_black_18dp.png
Binary files differ
diff --git a/samples-flatfoot/GithubBrowser/app/src/main/res/drawable-hdpi/ic_remove_red_eye_black_18dp.png b/samples-flatfoot/GithubBrowser/app/src/main/res/drawable-hdpi/ic_remove_red_eye_black_18dp.png
new file mode 100644
index 0000000..debb2b3
--- /dev/null
+++ b/samples-flatfoot/GithubBrowser/app/src/main/res/drawable-hdpi/ic_remove_red_eye_black_18dp.png
Binary files differ
diff --git a/samples-flatfoot/GithubBrowser/app/src/main/res/drawable-hdpi/ic_search_black_24dp.png b/samples-flatfoot/GithubBrowser/app/src/main/res/drawable-hdpi/ic_search_black_24dp.png
new file mode 100644
index 0000000..c593e7a
--- /dev/null
+++ b/samples-flatfoot/GithubBrowser/app/src/main/res/drawable-hdpi/ic_search_black_24dp.png
Binary files differ
diff --git a/samples-flatfoot/GithubBrowser/app/src/main/res/drawable-hdpi/ic_star_black_18dp.png b/samples-flatfoot/GithubBrowser/app/src/main/res/drawable-hdpi/ic_star_black_18dp.png
new file mode 100644
index 0000000..4ea8d0c
--- /dev/null
+++ b/samples-flatfoot/GithubBrowser/app/src/main/res/drawable-hdpi/ic_star_black_18dp.png
Binary files differ
diff --git a/samples-flatfoot/GithubBrowser/app/src/main/res/drawable-hdpi/ic_star_rate_black_18dp.png b/samples-flatfoot/GithubBrowser/app/src/main/res/drawable-hdpi/ic_star_rate_black_18dp.png
new file mode 100644
index 0000000..cd18bed
--- /dev/null
+++ b/samples-flatfoot/GithubBrowser/app/src/main/res/drawable-hdpi/ic_star_rate_black_18dp.png
Binary files differ
diff --git a/samples-flatfoot/GithubBrowser/app/src/main/res/drawable-hdpi/ic_watch_later_black_18dp.png b/samples-flatfoot/GithubBrowser/app/src/main/res/drawable-hdpi/ic_watch_later_black_18dp.png
new file mode 100644
index 0000000..c910f75
--- /dev/null
+++ b/samples-flatfoot/GithubBrowser/app/src/main/res/drawable-hdpi/ic_watch_later_black_18dp.png
Binary files differ
diff --git a/samples-flatfoot/GithubBrowser/app/src/main/res/drawable-mdpi/ic_bug_report_black_18dp.png b/samples-flatfoot/GithubBrowser/app/src/main/res/drawable-mdpi/ic_bug_report_black_18dp.png
new file mode 100644
index 0000000..b3d2e3e
--- /dev/null
+++ b/samples-flatfoot/GithubBrowser/app/src/main/res/drawable-mdpi/ic_bug_report_black_18dp.png
Binary files differ
diff --git a/samples-flatfoot/GithubBrowser/app/src/main/res/drawable-mdpi/ic_content_copy_black_18dp.png b/samples-flatfoot/GithubBrowser/app/src/main/res/drawable-mdpi/ic_content_copy_black_18dp.png
new file mode 100644
index 0000000..6363bc4
--- /dev/null
+++ b/samples-flatfoot/GithubBrowser/app/src/main/res/drawable-mdpi/ic_content_copy_black_18dp.png
Binary files differ
diff --git a/samples-flatfoot/GithubBrowser/app/src/main/res/drawable-mdpi/ic_email_black_18dp.png b/samples-flatfoot/GithubBrowser/app/src/main/res/drawable-mdpi/ic_email_black_18dp.png
new file mode 100644
index 0000000..977c4cb
--- /dev/null
+++ b/samples-flatfoot/GithubBrowser/app/src/main/res/drawable-mdpi/ic_email_black_18dp.png
Binary files differ
diff --git a/samples-flatfoot/GithubBrowser/app/src/main/res/drawable-mdpi/ic_link_black_18dp.png b/samples-flatfoot/GithubBrowser/app/src/main/res/drawable-mdpi/ic_link_black_18dp.png
new file mode 100644
index 0000000..6e5c394
--- /dev/null
+++ b/samples-flatfoot/GithubBrowser/app/src/main/res/drawable-mdpi/ic_link_black_18dp.png
Binary files differ
diff --git a/samples-flatfoot/GithubBrowser/app/src/main/res/drawable-mdpi/ic_location_on_black_18dp.png b/samples-flatfoot/GithubBrowser/app/src/main/res/drawable-mdpi/ic_location_on_black_18dp.png
new file mode 100644
index 0000000..b35923a
--- /dev/null
+++ b/samples-flatfoot/GithubBrowser/app/src/main/res/drawable-mdpi/ic_location_on_black_18dp.png
Binary files differ
diff --git a/samples-flatfoot/GithubBrowser/app/src/main/res/drawable-mdpi/ic_mode_edit_black_24dp.png b/samples-flatfoot/GithubBrowser/app/src/main/res/drawable-mdpi/ic_mode_edit_black_24dp.png
new file mode 100644
index 0000000..9efbaae
--- /dev/null
+++ b/samples-flatfoot/GithubBrowser/app/src/main/res/drawable-mdpi/ic_mode_edit_black_24dp.png
Binary files differ
diff --git a/samples-flatfoot/GithubBrowser/app/src/main/res/drawable-mdpi/ic_people_black_18dp.png b/samples-flatfoot/GithubBrowser/app/src/main/res/drawable-mdpi/ic_people_black_18dp.png
new file mode 100644
index 0000000..cc204fd
--- /dev/null
+++ b/samples-flatfoot/GithubBrowser/app/src/main/res/drawable-mdpi/ic_people_black_18dp.png
Binary files differ
diff --git a/samples-flatfoot/GithubBrowser/app/src/main/res/drawable-mdpi/ic_remove_red_eye_black_18dp.png b/samples-flatfoot/GithubBrowser/app/src/main/res/drawable-mdpi/ic_remove_red_eye_black_18dp.png
new file mode 100644
index 0000000..0f72bfa
--- /dev/null
+++ b/samples-flatfoot/GithubBrowser/app/src/main/res/drawable-mdpi/ic_remove_red_eye_black_18dp.png
Binary files differ
diff --git a/samples-flatfoot/GithubBrowser/app/src/main/res/drawable-mdpi/ic_search_black_24dp.png b/samples-flatfoot/GithubBrowser/app/src/main/res/drawable-mdpi/ic_search_black_24dp.png
new file mode 100644
index 0000000..6b16343
--- /dev/null
+++ b/samples-flatfoot/GithubBrowser/app/src/main/res/drawable-mdpi/ic_search_black_24dp.png
Binary files differ
diff --git a/samples-flatfoot/GithubBrowser/app/src/main/res/drawable-mdpi/ic_star_black_18dp.png b/samples-flatfoot/GithubBrowser/app/src/main/res/drawable-mdpi/ic_star_black_18dp.png
new file mode 100644
index 0000000..b125aa0
--- /dev/null
+++ b/samples-flatfoot/GithubBrowser/app/src/main/res/drawable-mdpi/ic_star_black_18dp.png
Binary files differ
diff --git a/samples-flatfoot/GithubBrowser/app/src/main/res/drawable-mdpi/ic_star_rate_black_18dp.png b/samples-flatfoot/GithubBrowser/app/src/main/res/drawable-mdpi/ic_star_rate_black_18dp.png
new file mode 100644
index 0000000..d6496ab
--- /dev/null
+++ b/samples-flatfoot/GithubBrowser/app/src/main/res/drawable-mdpi/ic_star_rate_black_18dp.png
Binary files differ
diff --git a/samples-flatfoot/GithubBrowser/app/src/main/res/drawable-mdpi/ic_watch_later_black_18dp.png b/samples-flatfoot/GithubBrowser/app/src/main/res/drawable-mdpi/ic_watch_later_black_18dp.png
new file mode 100644
index 0000000..19e22c0
--- /dev/null
+++ b/samples-flatfoot/GithubBrowser/app/src/main/res/drawable-mdpi/ic_watch_later_black_18dp.png
Binary files differ
diff --git a/samples-flatfoot/GithubBrowser/app/src/main/res/drawable-xhdpi/ic_bug_report_black_18dp.png b/samples-flatfoot/GithubBrowser/app/src/main/res/drawable-xhdpi/ic_bug_report_black_18dp.png
new file mode 100644
index 0000000..1bccb1d
--- /dev/null
+++ b/samples-flatfoot/GithubBrowser/app/src/main/res/drawable-xhdpi/ic_bug_report_black_18dp.png
Binary files differ
diff --git a/samples-flatfoot/GithubBrowser/app/src/main/res/drawable-xhdpi/ic_content_copy_black_18dp.png b/samples-flatfoot/GithubBrowser/app/src/main/res/drawable-xhdpi/ic_content_copy_black_18dp.png
new file mode 100644
index 0000000..9a9e570
--- /dev/null
+++ b/samples-flatfoot/GithubBrowser/app/src/main/res/drawable-xhdpi/ic_content_copy_black_18dp.png
Binary files differ
diff --git a/samples-flatfoot/GithubBrowser/app/src/main/res/drawable-xhdpi/ic_email_black_18dp.png b/samples-flatfoot/GithubBrowser/app/src/main/res/drawable-xhdpi/ic_email_black_18dp.png
new file mode 100644
index 0000000..36c6311
--- /dev/null
+++ b/samples-flatfoot/GithubBrowser/app/src/main/res/drawable-xhdpi/ic_email_black_18dp.png
Binary files differ
diff --git a/samples-flatfoot/GithubBrowser/app/src/main/res/drawable-xhdpi/ic_link_black_18dp.png b/samples-flatfoot/GithubBrowser/app/src/main/res/drawable-xhdpi/ic_link_black_18dp.png
new file mode 100644
index 0000000..76003e2
--- /dev/null
+++ b/samples-flatfoot/GithubBrowser/app/src/main/res/drawable-xhdpi/ic_link_black_18dp.png
Binary files differ
diff --git a/samples-flatfoot/GithubBrowser/app/src/main/res/drawable-xhdpi/ic_location_on_black_18dp.png b/samples-flatfoot/GithubBrowser/app/src/main/res/drawable-xhdpi/ic_location_on_black_18dp.png
new file mode 100644
index 0000000..df1f340
--- /dev/null
+++ b/samples-flatfoot/GithubBrowser/app/src/main/res/drawable-xhdpi/ic_location_on_black_18dp.png
Binary files differ
diff --git a/samples-flatfoot/GithubBrowser/app/src/main/res/drawable-xhdpi/ic_mode_edit_black_24dp.png b/samples-flatfoot/GithubBrowser/app/src/main/res/drawable-xhdpi/ic_mode_edit_black_24dp.png
new file mode 100644
index 0000000..87f8de1
--- /dev/null
+++ b/samples-flatfoot/GithubBrowser/app/src/main/res/drawable-xhdpi/ic_mode_edit_black_24dp.png
Binary files differ
diff --git a/samples-flatfoot/GithubBrowser/app/src/main/res/drawable-xhdpi/ic_people_black_18dp.png b/samples-flatfoot/GithubBrowser/app/src/main/res/drawable-xhdpi/ic_people_black_18dp.png
new file mode 100644
index 0000000..0782166
--- /dev/null
+++ b/samples-flatfoot/GithubBrowser/app/src/main/res/drawable-xhdpi/ic_people_black_18dp.png
Binary files differ
diff --git a/samples-flatfoot/GithubBrowser/app/src/main/res/drawable-xhdpi/ic_remove_red_eye_black_18dp.png b/samples-flatfoot/GithubBrowser/app/src/main/res/drawable-xhdpi/ic_remove_red_eye_black_18dp.png
new file mode 100644
index 0000000..329e617
--- /dev/null
+++ b/samples-flatfoot/GithubBrowser/app/src/main/res/drawable-xhdpi/ic_remove_red_eye_black_18dp.png
Binary files differ
diff --git a/samples-flatfoot/GithubBrowser/app/src/main/res/drawable-xhdpi/ic_search_black_24dp.png b/samples-flatfoot/GithubBrowser/app/src/main/res/drawable-xhdpi/ic_search_black_24dp.png
new file mode 100644
index 0000000..6381902
--- /dev/null
+++ b/samples-flatfoot/GithubBrowser/app/src/main/res/drawable-xhdpi/ic_search_black_24dp.png
Binary files differ
diff --git a/samples-flatfoot/GithubBrowser/app/src/main/res/drawable-xhdpi/ic_star_black_18dp.png b/samples-flatfoot/GithubBrowser/app/src/main/res/drawable-xhdpi/ic_star_black_18dp.png
new file mode 100644
index 0000000..92a0f58
--- /dev/null
+++ b/samples-flatfoot/GithubBrowser/app/src/main/res/drawable-xhdpi/ic_star_black_18dp.png
Binary files differ
diff --git a/samples-flatfoot/GithubBrowser/app/src/main/res/drawable-xhdpi/ic_star_rate_black_18dp.png b/samples-flatfoot/GithubBrowser/app/src/main/res/drawable-xhdpi/ic_star_rate_black_18dp.png
new file mode 100644
index 0000000..33a02af
--- /dev/null
+++ b/samples-flatfoot/GithubBrowser/app/src/main/res/drawable-xhdpi/ic_star_rate_black_18dp.png
Binary files differ
diff --git a/samples-flatfoot/GithubBrowser/app/src/main/res/drawable-xhdpi/ic_watch_later_black_18dp.png b/samples-flatfoot/GithubBrowser/app/src/main/res/drawable-xhdpi/ic_watch_later_black_18dp.png
new file mode 100644
index 0000000..c643539
--- /dev/null
+++ b/samples-flatfoot/GithubBrowser/app/src/main/res/drawable-xhdpi/ic_watch_later_black_18dp.png
Binary files differ
diff --git a/samples-flatfoot/GithubBrowser/app/src/main/res/drawable-xxhdpi/ic_bug_report_black_18dp.png b/samples-flatfoot/GithubBrowser/app/src/main/res/drawable-xxhdpi/ic_bug_report_black_18dp.png
new file mode 100644
index 0000000..145f2e6
--- /dev/null
+++ b/samples-flatfoot/GithubBrowser/app/src/main/res/drawable-xxhdpi/ic_bug_report_black_18dp.png
Binary files differ
diff --git a/samples-flatfoot/GithubBrowser/app/src/main/res/drawable-xxhdpi/ic_content_copy_black_18dp.png b/samples-flatfoot/GithubBrowser/app/src/main/res/drawable-xxhdpi/ic_content_copy_black_18dp.png
new file mode 100644
index 0000000..7ef1968
--- /dev/null
+++ b/samples-flatfoot/GithubBrowser/app/src/main/res/drawable-xxhdpi/ic_content_copy_black_18dp.png
Binary files differ
diff --git a/samples-flatfoot/GithubBrowser/app/src/main/res/drawable-xxhdpi/ic_email_black_18dp.png b/samples-flatfoot/GithubBrowser/app/src/main/res/drawable-xxhdpi/ic_email_black_18dp.png
new file mode 100644
index 0000000..0648fbd
--- /dev/null
+++ b/samples-flatfoot/GithubBrowser/app/src/main/res/drawable-xxhdpi/ic_email_black_18dp.png
Binary files differ
diff --git a/samples-flatfoot/GithubBrowser/app/src/main/res/drawable-xxhdpi/ic_link_black_18dp.png b/samples-flatfoot/GithubBrowser/app/src/main/res/drawable-xxhdpi/ic_link_black_18dp.png
new file mode 100644
index 0000000..69ea7ef
--- /dev/null
+++ b/samples-flatfoot/GithubBrowser/app/src/main/res/drawable-xxhdpi/ic_link_black_18dp.png
Binary files differ
diff --git a/samples-flatfoot/GithubBrowser/app/src/main/res/drawable-xxhdpi/ic_location_on_black_18dp.png b/samples-flatfoot/GithubBrowser/app/src/main/res/drawable-xxhdpi/ic_location_on_black_18dp.png
new file mode 100644
index 0000000..ebe833b
--- /dev/null
+++ b/samples-flatfoot/GithubBrowser/app/src/main/res/drawable-xxhdpi/ic_location_on_black_18dp.png
Binary files differ
diff --git a/samples-flatfoot/GithubBrowser/app/src/main/res/drawable-xxhdpi/ic_mode_edit_black_24dp.png b/samples-flatfoot/GithubBrowser/app/src/main/res/drawable-xxhdpi/ic_mode_edit_black_24dp.png
new file mode 100644
index 0000000..4af4ae6
--- /dev/null
+++ b/samples-flatfoot/GithubBrowser/app/src/main/res/drawable-xxhdpi/ic_mode_edit_black_24dp.png
Binary files differ
diff --git a/samples-flatfoot/GithubBrowser/app/src/main/res/drawable-xxhdpi/ic_people_black_18dp.png b/samples-flatfoot/GithubBrowser/app/src/main/res/drawable-xxhdpi/ic_people_black_18dp.png
new file mode 100644
index 0000000..a595b2e
--- /dev/null
+++ b/samples-flatfoot/GithubBrowser/app/src/main/res/drawable-xxhdpi/ic_people_black_18dp.png
Binary files differ
diff --git a/samples-flatfoot/GithubBrowser/app/src/main/res/drawable-xxhdpi/ic_remove_red_eye_black_18dp.png b/samples-flatfoot/GithubBrowser/app/src/main/res/drawable-xxhdpi/ic_remove_red_eye_black_18dp.png
new file mode 100644
index 0000000..2e54e32
--- /dev/null
+++ b/samples-flatfoot/GithubBrowser/app/src/main/res/drawable-xxhdpi/ic_remove_red_eye_black_18dp.png
Binary files differ
diff --git a/samples-flatfoot/GithubBrowser/app/src/main/res/drawable-xxhdpi/ic_search_black_24dp.png b/samples-flatfoot/GithubBrowser/app/src/main/res/drawable-xxhdpi/ic_search_black_24dp.png
new file mode 100644
index 0000000..3ae490e
--- /dev/null
+++ b/samples-flatfoot/GithubBrowser/app/src/main/res/drawable-xxhdpi/ic_search_black_24dp.png
Binary files differ
diff --git a/samples-flatfoot/GithubBrowser/app/src/main/res/drawable-xxhdpi/ic_star_black_18dp.png b/samples-flatfoot/GithubBrowser/app/src/main/res/drawable-xxhdpi/ic_star_black_18dp.png
new file mode 100644
index 0000000..4f67f97
--- /dev/null
+++ b/samples-flatfoot/GithubBrowser/app/src/main/res/drawable-xxhdpi/ic_star_black_18dp.png
Binary files differ
diff --git a/samples-flatfoot/GithubBrowser/app/src/main/res/drawable-xxhdpi/ic_star_rate_black_18dp.png b/samples-flatfoot/GithubBrowser/app/src/main/res/drawable-xxhdpi/ic_star_rate_black_18dp.png
new file mode 100644
index 0000000..658b08d
--- /dev/null
+++ b/samples-flatfoot/GithubBrowser/app/src/main/res/drawable-xxhdpi/ic_star_rate_black_18dp.png
Binary files differ
diff --git a/samples-flatfoot/GithubBrowser/app/src/main/res/drawable-xxhdpi/ic_watch_later_black_18dp.png b/samples-flatfoot/GithubBrowser/app/src/main/res/drawable-xxhdpi/ic_watch_later_black_18dp.png
new file mode 100644
index 0000000..3b41d99
--- /dev/null
+++ b/samples-flatfoot/GithubBrowser/app/src/main/res/drawable-xxhdpi/ic_watch_later_black_18dp.png
Binary files differ
diff --git a/samples-flatfoot/GithubBrowser/app/src/main/res/drawable-xxxhdpi/ic_bug_report_black_18dp.png b/samples-flatfoot/GithubBrowser/app/src/main/res/drawable-xxxhdpi/ic_bug_report_black_18dp.png
new file mode 100644
index 0000000..af8c82e
--- /dev/null
+++ b/samples-flatfoot/GithubBrowser/app/src/main/res/drawable-xxxhdpi/ic_bug_report_black_18dp.png
Binary files differ
diff --git a/samples-flatfoot/GithubBrowser/app/src/main/res/drawable-xxxhdpi/ic_content_copy_black_18dp.png b/samples-flatfoot/GithubBrowser/app/src/main/res/drawable-xxxhdpi/ic_content_copy_black_18dp.png
new file mode 100644
index 0000000..074ea88
--- /dev/null
+++ b/samples-flatfoot/GithubBrowser/app/src/main/res/drawable-xxxhdpi/ic_content_copy_black_18dp.png
Binary files differ
diff --git a/samples-flatfoot/GithubBrowser/app/src/main/res/drawable-xxxhdpi/ic_email_black_18dp.png b/samples-flatfoot/GithubBrowser/app/src/main/res/drawable-xxxhdpi/ic_email_black_18dp.png
new file mode 100644
index 0000000..3d13627
--- /dev/null
+++ b/samples-flatfoot/GithubBrowser/app/src/main/res/drawable-xxxhdpi/ic_email_black_18dp.png
Binary files differ
diff --git a/samples-flatfoot/GithubBrowser/app/src/main/res/drawable-xxxhdpi/ic_link_black_18dp.png b/samples-flatfoot/GithubBrowser/app/src/main/res/drawable-xxxhdpi/ic_link_black_18dp.png
new file mode 100644
index 0000000..af03b85
--- /dev/null
+++ b/samples-flatfoot/GithubBrowser/app/src/main/res/drawable-xxxhdpi/ic_link_black_18dp.png
Binary files differ
diff --git a/samples-flatfoot/GithubBrowser/app/src/main/res/drawable-xxxhdpi/ic_location_on_black_18dp.png b/samples-flatfoot/GithubBrowser/app/src/main/res/drawable-xxxhdpi/ic_location_on_black_18dp.png
new file mode 100644
index 0000000..5a21dfa
--- /dev/null
+++ b/samples-flatfoot/GithubBrowser/app/src/main/res/drawable-xxxhdpi/ic_location_on_black_18dp.png
Binary files differ
diff --git a/samples-flatfoot/GithubBrowser/app/src/main/res/drawable-xxxhdpi/ic_mode_edit_black_24dp.png b/samples-flatfoot/GithubBrowser/app/src/main/res/drawable-xxxhdpi/ic_mode_edit_black_24dp.png
new file mode 100644
index 0000000..d6761ba
--- /dev/null
+++ b/samples-flatfoot/GithubBrowser/app/src/main/res/drawable-xxxhdpi/ic_mode_edit_black_24dp.png
Binary files differ
diff --git a/samples-flatfoot/GithubBrowser/app/src/main/res/drawable-xxxhdpi/ic_people_black_18dp.png b/samples-flatfoot/GithubBrowser/app/src/main/res/drawable-xxxhdpi/ic_people_black_18dp.png
new file mode 100644
index 0000000..5a8b5d0
--- /dev/null
+++ b/samples-flatfoot/GithubBrowser/app/src/main/res/drawable-xxxhdpi/ic_people_black_18dp.png
Binary files differ
diff --git a/samples-flatfoot/GithubBrowser/app/src/main/res/drawable-xxxhdpi/ic_remove_red_eye_black_18dp.png b/samples-flatfoot/GithubBrowser/app/src/main/res/drawable-xxxhdpi/ic_remove_red_eye_black_18dp.png
new file mode 100644
index 0000000..c816ab4
--- /dev/null
+++ b/samples-flatfoot/GithubBrowser/app/src/main/res/drawable-xxxhdpi/ic_remove_red_eye_black_18dp.png
Binary files differ
diff --git a/samples-flatfoot/GithubBrowser/app/src/main/res/drawable-xxxhdpi/ic_search_black_24dp.png b/samples-flatfoot/GithubBrowser/app/src/main/res/drawable-xxxhdpi/ic_search_black_24dp.png
new file mode 100644
index 0000000..21be572
--- /dev/null
+++ b/samples-flatfoot/GithubBrowser/app/src/main/res/drawable-xxxhdpi/ic_search_black_24dp.png
Binary files differ
diff --git a/samples-flatfoot/GithubBrowser/app/src/main/res/drawable-xxxhdpi/ic_star_black_18dp.png b/samples-flatfoot/GithubBrowser/app/src/main/res/drawable-xxxhdpi/ic_star_black_18dp.png
new file mode 100644
index 0000000..54d3065
--- /dev/null
+++ b/samples-flatfoot/GithubBrowser/app/src/main/res/drawable-xxxhdpi/ic_star_black_18dp.png
Binary files differ
diff --git a/samples-flatfoot/GithubBrowser/app/src/main/res/drawable-xxxhdpi/ic_star_rate_black_18dp.png b/samples-flatfoot/GithubBrowser/app/src/main/res/drawable-xxxhdpi/ic_star_rate_black_18dp.png
new file mode 100644
index 0000000..1823bbb
--- /dev/null
+++ b/samples-flatfoot/GithubBrowser/app/src/main/res/drawable-xxxhdpi/ic_star_rate_black_18dp.png
Binary files differ
diff --git a/samples-flatfoot/GithubBrowser/app/src/main/res/drawable-xxxhdpi/ic_watch_later_black_18dp.png b/samples-flatfoot/GithubBrowser/app/src/main/res/drawable-xxxhdpi/ic_watch_later_black_18dp.png
new file mode 100644
index 0000000..bfb296d
--- /dev/null
+++ b/samples-flatfoot/GithubBrowser/app/src/main/res/drawable-xxxhdpi/ic_watch_later_black_18dp.png
Binary files differ
diff --git a/samples-flatfoot/GithubBrowser/app/src/main/res/layout/activity_main.xml b/samples-flatfoot/GithubBrowser/app/src/main/res/layout/activity_main.xml
new file mode 100644
index 0000000..7aacfcb
--- /dev/null
+++ b/samples-flatfoot/GithubBrowser/app/src/main/res/layout/activity_main.xml
@@ -0,0 +1,67 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  ~ Copyright (C) 2017 The Android Open Source Project
+  ~
+  ~ Licensed under the Apache License, Version 2.0 (the "License");
+  ~ you may not use this file except in compliance with the License.
+  ~ You may obtain a copy of the License at
+  ~
+  ~      http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing, software
+  ~ distributed under the License is distributed on an "AS IS" BASIS,
+  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  ~ See the License for the specific language governing permissions and
+  ~ limitations under the License.
+  -->
+
+<android.support.design.widget.CoordinatorLayout
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:app="http://schemas.android.com/apk/res-auto"
+    xmlns:tools="http://schemas.android.com/tools"
+    android:id="@+id/col"
+    android:layout_width="match_parent"
+    android:layout_height="match_parent"
+    android:fitsSystemWindows="true"
+    tools:context="com.android.sample.githubbrowser.MainActivity">
+
+    <android.support.design.widget.AppBarLayout
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        android:theme="@style/AppTheme.AppBarOverlay">
+
+        <android.support.v7.widget.Toolbar
+            android:id="@+id/toolbar"
+            android:layout_width="match_parent"
+            android:layout_height="?attr/actionBarSize"
+            android:background="?attr/colorPrimary"
+            app:contentInsetStart="0dp"
+            app:popupTheme="@style/AppTheme.PopupOverlay">
+
+            <android.support.v7.widget.CardView
+                android:id="@+id/card_view"
+                android:layout_width="match_parent"
+                android:layout_height="match_parent"
+                android:layout_margin="8dip"
+                app:cardCornerRadius="4dp"
+                app:cardElevation="8dp">
+
+                <EditText
+                    android:id="@+id/search"
+                    android:layout_width="match_parent"
+                    android:layout_height="wrap_content"
+                    android:layout_gravity="center_vertical"
+                    android:background="@null"
+                    android:drawableRight="@drawable/ic_search_black_24dp"
+                    android:paddingLeft="12dp"
+                    android:paddingRight="12dp"
+                    android:maxLines="1"
+                    android:inputType="textNoSuggestions"
+                    android:textColor="@color/colorPrimary"/>
+            </android.support.v7.widget.CardView>
+        </android.support.v7.widget.Toolbar>
+
+    </android.support.design.widget.AppBarLayout>
+
+    <include layout="@layout/content_main"/>
+</android.support.design.widget.CoordinatorLayout>
diff --git a/samples-flatfoot/GithubBrowser/app/src/main/res/layout/content_main.xml b/samples-flatfoot/GithubBrowser/app/src/main/res/layout/content_main.xml
new file mode 100644
index 0000000..ad98eda
--- /dev/null
+++ b/samples-flatfoot/GithubBrowser/app/src/main/res/layout/content_main.xml
@@ -0,0 +1,22 @@
+<!--
+  ~ Copyright (C) 2017 The Android Open Source Project
+  ~
+  ~ Licensed under the Apache License, Version 2.0 (the "License");
+  ~ you may not use this file except in compliance with the License.
+  ~ You may obtain a copy of the License at
+  ~
+  ~      http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing, software
+  ~ distributed under the License is distributed on an "AS IS" BASIS,
+  ~ WITHOUT 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 android:id="@+id/fragment_container"
+             xmlns:android="http://schemas.android.com/apk/res/android"
+             xmlns:app="http://schemas.android.com/apk/res-auto"
+             android:layout_width="match_parent"
+             android:layout_height="match_parent"
+             app:layout_behavior="@string/appbar_scrolling_view_behavior"/>
diff --git a/samples-flatfoot/GithubBrowser/app/src/main/res/layout/edit_user_details.xml b/samples-flatfoot/GithubBrowser/app/src/main/res/layout/edit_user_details.xml
new file mode 100644
index 0000000..3113520
--- /dev/null
+++ b/samples-flatfoot/GithubBrowser/app/src/main/res/layout/edit_user_details.xml
@@ -0,0 +1,56 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  ~ Copyright (C) 2017 The Android Open Source Project
+  ~
+  ~ Licensed under the Apache License, Version 2.0 (the "License");
+  ~ you may not use this file except in compliance with the License.
+  ~ You may obtain a copy of the License at
+  ~
+  ~      http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing, software
+  ~ distributed under the License is distributed on an "AS IS" BASIS,
+  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  ~ See the License for the specific language governing permissions and
+  ~ limitations under the License.
+  -->
+
+<layout xmlns:android="http://schemas.android.com/apk/res/android">
+    <data>
+        <variable name="user" type="com.android.sample.githubbrowser.data.PersonData"/>
+    </data>
+
+    <LinearLayout
+        android:layout_width="match_parent"
+        android:layout_height="match_parent"
+        android:orientation="vertical"
+        android:paddingTop="16dp"
+        android:paddingLeft="8dp"
+        android:paddingRight="8dp">
+
+        <android.support.design.widget.TextInputLayout
+            android:id="@+id/wrapper_email"
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content">
+            <EditText
+                android:id="@+id/email"
+                android:layout_width="match_parent"
+                android:layout_height="wrap_content"
+                android:hint="@string/hint_email"
+                android:text="@{user.email}" />
+        </android.support.design.widget.TextInputLayout>
+
+        <android.support.design.widget.TextInputLayout
+            android:id="@+id/wrapper_location"
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content">
+            <EditText
+                android:id="@+id/location"
+                android:layout_width="match_parent"
+                android:layout_height="wrap_content"
+                android:hint="@string/hint_location"
+                android:text="@{user.location}" />
+        </android.support.design.widget.TextInputLayout>
+
+    </LinearLayout>
+</layout>
diff --git a/samples-flatfoot/GithubBrowser/app/src/main/res/layout/fragment_repo_details.xml b/samples-flatfoot/GithubBrowser/app/src/main/res/layout/fragment_repo_details.xml
new file mode 100644
index 0000000..8727bb8
--- /dev/null
+++ b/samples-flatfoot/GithubBrowser/app/src/main/res/layout/fragment_repo_details.xml
@@ -0,0 +1,155 @@
+<?xml version="1.0" encoding="utf-8"?>
+
+<!--
+  ~ Copyright (C) 2017 The Android Open Source Project
+  ~
+  ~ Licensed under the Apache License, Version 2.0 (the "License");
+  ~ you may not use this file except in compliance with the License.
+  ~ You may obtain a copy of the License at
+  ~
+  ~      http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing, software
+  ~ distributed under the License is distributed on an "AS IS" BASIS,
+  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  ~ See the License for the specific language governing permissions and
+  ~ limitations under the License.
+  -->
+
+<layout
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:app="http://schemas.android.com/apk/res-auto">
+    <data>
+        <variable name="repo" type="com.android.sample.githubbrowser.data.RepositoryData"/>
+    </data>
+
+    <LinearLayout
+        android:layout_width="match_parent"
+        android:layout_height="match_parent"
+        android:background="@color/background"
+        android:orientation="vertical">
+
+        <RelativeLayout
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content">
+
+            <ImageView
+                android:id="@+id/avatar"
+                android:layout_width="96dp"
+                android:layout_height="96dp"
+                android:background="#FAFAFA"
+                app:imageUrl="@{repo.owner.avatar_url}"/>
+
+            <TextView
+                android:id="@+id/name"
+                android:layout_width="match_parent"
+                android:layout_height="wrap_content"
+                android:layout_toRightOf="@id/avatar"
+                android:lines="1"
+                android:maxLines="1"
+                android:paddingLeft="8dp"
+                android:paddingRight="8dp"
+                android:paddingTop="4dp"
+                android:text="@{repo.name}"
+                android:textColor="#222"
+                android:textSize="20sp"/>
+
+            <TextView
+                android:id="@+id/description"
+                android:layout_width="match_parent"
+                android:layout_height="wrap_content"
+                android:layout_below="@id/name"
+                android:layout_toRightOf="@id/avatar"
+                android:lines="2"
+                android:maxLines="2"
+                android:paddingLeft="8dp"
+                android:paddingRight="8dp"
+                android:paddingTop="4dp"
+                android:text="@{repo.description}"
+                android:textColor="#444"
+                android:textSize="14sp"/>
+
+            <TextView
+                android:id="@+id/created"
+                android:layout_width="match_parent"
+                android:layout_height="wrap_content"
+                android:layout_alignBottom="@id/avatar"
+                android:layout_toRightOf="@id/avatar"
+                android:lines="1"
+                android:maxLines="1"
+                android:paddingBottom="2dp"
+                android:paddingLeft="8dp"
+                android:textColor="#444"
+                android:textSize="14sp"
+                app:jsonDate="@{repo.created_at}"/>
+
+            <TextView
+                android:id="@+id/bugs"
+                android:layout_width="wrap_content"
+                android:layout_height="wrap_content"
+                android:layout_below="@id/avatar"
+                android:drawableLeft="@drawable/ic_bug_report_black_18dp"
+                android:drawablePadding="2dp"
+                android:lines="1"
+                android:maxLines="1"
+                android:paddingLeft="16dp"
+                android:paddingTop="8dp"
+                android:text="@{Integer.toString(repo.open_issues_count)}"
+                android:textColor="#444"
+                android:textSize="14sp"/>
+
+            <TextView
+                android:id="@+id/starred"
+                android:layout_width="wrap_content"
+                android:layout_height="wrap_content"
+                android:layout_below="@id/avatar"
+                android:layout_toRightOf="@id/bugs"
+                android:drawableLeft="@drawable/ic_star_black_18dp"
+                android:drawablePadding="2dp"
+                android:lines="1"
+                android:maxLines="1"
+                android:paddingLeft="16dp"
+                android:paddingTop="8dp"
+                android:text="@{Integer.toString(repo.stargazers_count)}"
+                android:textColor="#444"
+                android:textSize="14sp"/>
+
+            <TextView
+                android:id="@+id/forked"
+                android:layout_width="wrap_content"
+                android:layout_height="wrap_content"
+                android:layout_below="@id/avatar"
+                android:layout_toRightOf="@id/starred"
+                android:drawableLeft="@drawable/ic_content_copy_black_18dp"
+                android:drawablePadding="2dp"
+                android:lines="1"
+                android:maxLines="1"
+                android:paddingLeft="16dp"
+                android:paddingRight="10dp"
+                android:paddingTop="8dp"
+                android:text="@{Integer.toString(repo.forks_count)}"
+                android:textColor="#444"
+                android:textSize="14sp"/>
+
+        </RelativeLayout>
+
+
+        <TextView
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:paddingBottom="4dp"
+            android:paddingLeft="12dp"
+            android:paddingTop="24dp"
+            android:text="@string/contributors"
+            android:textAppearance="@style/TextAppearance.AppCompat.Body2"
+            app:textAllCaps="true"/>
+
+        <android.support.v7.widget.RecyclerView
+            android:id="@+id/contributors"
+            android:layout_width="match_parent"
+            app:layoutManager="LinearLayoutManager"
+            android:layout_height="0dip"
+            android:layout_weight="1"/>
+
+    </LinearLayout>
+</layout>
diff --git a/samples-flatfoot/GithubBrowser/app/src/main/res/layout/fragment_repo_list.xml b/samples-flatfoot/GithubBrowser/app/src/main/res/layout/fragment_repo_list.xml
new file mode 100644
index 0000000..8424df0
--- /dev/null
+++ b/samples-flatfoot/GithubBrowser/app/src/main/res/layout/fragment_repo_list.xml
@@ -0,0 +1,60 @@
+<?xml version="1.0" encoding="utf-8"?>
+
+<!--
+  ~ Copyright (C) 2017 The Android Open Source Project
+  ~
+  ~ Licensed under the Apache License, Version 2.0 (the "License");
+  ~ you may not use this file except in compliance with the License.
+  ~ You may obtain a copy of the License at
+  ~
+  ~      http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing, software
+  ~ distributed under the License is distributed on an "AS IS" BASIS,
+  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  ~ See the License for the specific language governing permissions and
+  ~ limitations under the License.
+  -->
+
+<layout xmlns:android="http://schemas.android.com/apk/res/android"
+        xmlns:app="http://schemas.android.com/apk/res-auto">
+    <data>
+        <import type="java.lang.String"/>
+        <import type="com.android.sample.githubbrowser.viewmodel.RepositoryListModel"/>
+        <variable name="state" type="Integer"/>
+        <variable name="query" type="String"/>
+    </data>
+
+    <FrameLayout
+        android:layout_width="match_parent"
+        android:layout_height="match_parent"
+        android:background="@color/background">
+
+        <ProgressBar
+            android:id="@+id/loading"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:layout_gravity="center"
+            android:indeterminate="true"
+            app:visibleInvisible="@{state == RepositoryListModel.STATE_INITIAL_LOADING}"/>
+
+        <TextView
+            android:id="@+id/status"
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:layout_gravity="center"
+            android:gravity="center"
+            android:paddingLeft="24dp"
+            android:paddingRight="24dp"
+            android:textSize="20sp"
+            android:text="@{state == RepositoryListModel.STATE_EMPTY ? @string/no_results(query) : @string/load_error(query)}"
+            app:visibleInvisible="@{state == RepositoryListModel.STATE_EMPTY || state == RepositoryListModel.STATE_ERROR}"/>
+
+        <android.support.v7.widget.RecyclerView
+            android:id="@+id/repo_list"
+            android:layout_width="match_parent"
+            android:layout_height="match_parent"
+            app:visibleInvisible="@{state == RepositoryListModel.STATE_DATA}"/>
+
+    </FrameLayout>
+</layout>
\ No newline at end of file
diff --git a/samples-flatfoot/GithubBrowser/app/src/main/res/layout/fragment_user_details.xml b/samples-flatfoot/GithubBrowser/app/src/main/res/layout/fragment_user_details.xml
new file mode 100644
index 0000000..3ed4e05
--- /dev/null
+++ b/samples-flatfoot/GithubBrowser/app/src/main/res/layout/fragment_user_details.xml
@@ -0,0 +1,257 @@
+<?xml version="1.0" encoding="utf-8"?>
+
+<!--
+  ~ Copyright (C) 2017 The Android Open Source Project
+  ~
+  ~ Licensed under the Apache License, Version 2.0 (the "License");
+  ~ you may not use this file except in compliance with the License.
+  ~ You may obtain a copy of the License at
+  ~
+  ~      http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing, software
+  ~ distributed under the License is distributed on an "AS IS" BASIS,
+  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  ~ See the License for the specific language governing permissions and
+  ~ limitations under the License.
+  -->
+
+<layout
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:app="http://schemas.android.com/apk/res-auto">
+    <data>
+        <import type="android.text.TextUtils"/>
+        <import type="com.android.sample.githubbrowser.R"/>
+        <variable name="user" type="com.android.sample.githubbrowser.data.PersonData"/>
+        <variable name="editCallback" type="com.android.sample.githubbrowser.view.PersonClickCallback"/>
+    </data>
+
+    <LinearLayout
+        android:layout_width="match_parent"
+        android:layout_height="match_parent"
+        android:background="@color/background"
+        android:orientation="vertical">
+
+        <RelativeLayout
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content">
+
+            <ImageView
+                android:id="@+id/avatar"
+                android:layout_width="96dp"
+                android:layout_height="96dp"
+                android:background="#FAFAFA"
+                app:imageUrl="@{user.avatar_url}"/>
+
+            <TextView
+                android:id="@+id/name"
+                android:layout_width="match_parent"
+                android:layout_height="wrap_content"
+                android:layout_toRightOf="@id/avatar"
+                android:lines="1"
+                android:maxLines="1"
+                android:paddingLeft="8dp"
+                android:paddingRight="8dp"
+                android:paddingTop="4dp"
+                android:text="@{user.name}"
+                android:textColor="#222"
+                android:textSize="20sp"
+                app:visibleInvisible="@{!(user == null || TextUtils.isEmpty(user.name))}"/>
+
+            <TextView
+                android:id="@+id/login"
+                android:layout_width="match_parent"
+                android:layout_height="wrap_content"
+                android:layout_below="@id/name"
+                android:layout_toRightOf="@id/avatar"
+                android:lines="1"
+                android:maxLines="1"
+                android:paddingLeft="8dp"
+                android:paddingRight="8dp"
+                android:paddingTop="4dp"
+                android:text="@{user.login}"
+                android:textColor="#444"
+                android:textSize="18sp"/>
+
+            <TextView
+                android:id="@+id/created"
+                android:layout_width="match_parent"
+                android:layout_height="wrap_content"
+                android:layout_alignBottom="@id/avatar"
+                android:layout_toRightOf="@id/avatar"
+                android:lines="1"
+                android:maxLines="1"
+                android:paddingBottom="2dp"
+                android:paddingLeft="8dp"
+                android:textColor="#444"
+                android:textSize="14sp"
+                app:visibleInvisible="@{!(user == null || TextUtils.isEmpty(user.created_at))}"
+                app:jsonDate="@{user.created_at}"
+                app:stringRes="@{R.string.joined}"/>
+
+        </RelativeLayout>
+
+        <LinearLayout
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:orientation="horizontal"
+            android:paddingLeft="16dp"
+            android:paddingTop="8dp">
+            <TextView
+                android:id="@+id/company"
+                android:layout_width="wrap_content"
+                android:layout_height="wrap_content"
+                android:drawableLeft="@drawable/ic_people_black_18dp"
+                android:drawablePadding="2dp"
+                android:lines="1"
+                android:maxLines="1"
+                android:text="@{user.company}"
+                android:textColor="#444"
+                android:textSize="14sp"
+                app:visibleGone="@{!(user == null || TextUtils.isEmpty(user.company))}"/>
+
+            <TextView
+                android:id="@+id/location"
+                android:layout_width="wrap_content"
+                android:layout_height="wrap_content"
+                android:drawableLeft="@drawable/ic_location_on_black_18dp"
+                android:drawablePadding="2dp"
+                android:lines="1"
+                android:maxLines="1"
+                android:paddingLeft="16dp"
+                android:text="@{user.location}"
+                android:textColor="#444"
+                android:textSize="14sp"
+                app:visibleGone="@{!(user == null || TextUtils.isEmpty(user.location))}"/>
+
+            <View
+                android:layout_width="0dp"
+                android:layout_height="0dp"
+                android:layout_weight="1"/>
+
+            <ImageButton
+                android:id="@+id/edit"
+                android:layout_width="wrap_content"
+                android:layout_height="wrap_content"
+                android:layout_marginRight="8dp"
+                android:background="@null"
+                android:clickable="true"
+                android:onClick="@{() -> editCallback.onClick(user)}"
+                android:src="@drawable/ic_mode_edit_black_24dp"
+                app:visibleGone="@{user != null}"/>
+        </LinearLayout>
+
+        <LinearLayout
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:orientation="horizontal"
+            android:paddingLeft="16dp"
+            android:paddingTop="8dp">
+            <TextView
+                android:id="@+id/email"
+                android:layout_width="wrap_content"
+                android:layout_height="wrap_content"
+                android:drawableLeft="@drawable/ic_email_black_18dp"
+                android:drawablePadding="2dp"
+                android:lines="1"
+                android:maxLines="1"
+                android:text="@{user.email}"
+                android:textColor="#444"
+                android:textSize="14sp"
+                app:visibleGone="@{!(user == null || TextUtils.isEmpty(user.email))}"/>
+
+            <TextView
+                android:id="@+id/blog"
+                android:layout_width="wrap_content"
+                android:layout_height="wrap_content"
+                android:drawableLeft="@drawable/ic_link_black_18dp"
+                android:drawablePadding="2dp"
+                android:lines="1"
+                android:maxLines="1"
+                android:paddingLeft="16dp"
+                android:text="@{user.blog}"
+                android:textColor="#444"
+                android:textSize="14sp"
+                app:visibleGone="@{!(user == null || TextUtils.isEmpty(user.blog))}"/>
+        </LinearLayout>
+
+        <LinearLayout
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:orientation="horizontal"
+            android:paddingLeft="16dp"
+            android:paddingTop="8dp">
+            <TextView
+                android:id="@+id/followers_label"
+                android:layout_width="wrap_content"
+                android:layout_height="wrap_content"
+                android:lines="1"
+                android:maxLines="1"
+                android:paddingRight="4dp"
+                android:text="@string/followers"
+                android:textColor="#444"
+                android:textSize="14sp"
+                app:visibleGone="@{user != null}"/>
+
+            <TextView
+                android:id="@+id/followers"
+                android:layout_width="wrap_content"
+                android:layout_height="wrap_content"
+                android:background="#CCC"
+                android:lines="1"
+                android:maxLines="1"
+                android:paddingLeft="4dp"
+                android:paddingRight="4dp"
+                android:text="@{Integer.toString(user.followers)}"
+                android:textColor="#444"
+                android:textSize="14sp"
+                app:visibleGone="@{user != null}"/>
+
+            <TextView
+                android:id="@+id/following_label"
+                android:layout_width="wrap_content"
+                android:layout_height="wrap_content"
+                android:lines="1"
+                android:maxLines="1"
+                android:paddingLeft="16dp"
+                android:paddingRight="4dp"
+                android:text="@string/following"
+                android:textColor="#444"
+                android:textSize="14sp"
+                app:visibleGone="@{user != null}"/>
+
+            <TextView
+                android:id="@+id/following"
+                android:layout_width="wrap_content"
+                android:layout_height="wrap_content"
+                android:background="#CCC"
+                android:lines="1"
+                android:maxLines="1"
+                android:paddingLeft="4dp"
+                android:paddingRight="4dp"
+                android:text="@{Integer.toString(user.following)}"
+                android:textColor="#444"
+                android:textSize="14sp"
+                app:visibleGone="@{user != null}"/>
+        </LinearLayout>
+
+        <TextView
+            android:id="@+id/repositories_header"
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:paddingBottom="4dp"
+            android:paddingLeft="12dp"
+            android:paddingTop="24dp"
+            android:text="@string/repositories"
+            android:textAppearance="@style/TextAppearance.AppCompat.Body2"
+            app:visibleGone="@{user != null}"/>
+            app:textAllCaps="true"/>
+
+        <android.support.v7.widget.RecyclerView
+            android:id="@+id/repositories"
+            android:layout_width="match_parent"
+            android:layout_height="0dip"
+            android:layout_weight="1"/>
+
+    </LinearLayout>
+</layout>
\ No newline at end of file
diff --git a/samples-flatfoot/GithubBrowser/app/src/main/res/layout/get_auth_token.xml b/samples-flatfoot/GithubBrowser/app/src/main/res/layout/get_auth_token.xml
new file mode 100644
index 0000000..9547f40
--- /dev/null
+++ b/samples-flatfoot/GithubBrowser/app/src/main/res/layout/get_auth_token.xml
@@ -0,0 +1,37 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  ~ Copyright (C) 2017 The Android Open Source Project
+  ~
+  ~ Licensed under the Apache License, Version 2.0 (the "License");
+  ~ you may not use this file except in compliance with the License.
+  ~ You may obtain a copy of the License at
+  ~
+  ~      http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing, software
+  ~ distributed under the License is distributed on an "AS IS" BASIS,
+  ~ WITHOUT 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:paddingTop="16dp"
+    android:paddingLeft="8dp"
+    android:paddingRight="8dp">
+
+    <android.support.design.widget.TextInputLayout
+        android:id="@+id/wrapper_token"
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content">
+        <EditText
+            android:id="@+id/token"
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:hint="@string/hint_token"/>
+    </android.support.design.widget.TextInputLayout>
+
+</FrameLayout>
\ No newline at end of file
diff --git a/samples-flatfoot/GithubBrowser/app/src/main/res/layout/repository_card.xml b/samples-flatfoot/GithubBrowser/app/src/main/res/layout/repository_card.xml
new file mode 100644
index 0000000..dc2f6b8
--- /dev/null
+++ b/samples-flatfoot/GithubBrowser/app/src/main/res/layout/repository_card.xml
@@ -0,0 +1,140 @@
+<?xml version="1.0" encoding="utf-8"?>
+
+<!--
+  ~ Copyright (C) 2017 The Android Open Source Project
+  ~
+  ~ Licensed under the Apache License, Version 2.0 (the "License");
+  ~ you may not use this file except in compliance with the License.
+  ~ You may obtain a copy of the License at
+  ~
+  ~      http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing, software
+  ~ distributed under the License is distributed on an "AS IS" BASIS,
+  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  ~ See the License for the specific language governing permissions and
+  ~ limitations under the License.
+  -->
+
+<layout
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:app="http://schemas.android.com/apk/res-auto">
+    <data>
+        <variable name="repo" type="com.android.sample.githubbrowser.data.RepositoryData"/>
+        <variable name="repoClickCallback"
+                  type="com.android.sample.githubbrowser.view.RepoClickCallback"/>
+    </data>
+
+    <FrameLayout
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        android:onClick="@{() -> repoClickCallback.onClick(repo)}">
+
+        <android.support.v7.widget.CardView
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:layout_marginBottom="4dp"
+            android:layout_marginLeft="10dp"
+            android:layout_marginRight="10dp"
+            android:layout_marginTop="6dp"
+            app:cardCornerRadius="4dp"
+            app:cardElevation="2dp">
+
+            <RelativeLayout
+                android:layout_width="match_parent"
+                android:layout_height="96dp">
+
+                <TextView
+                    android:id="@+id/name"
+                    android:layout_width="match_parent"
+                    android:layout_height="wrap_content"
+                    android:lines="1"
+                    android:maxLines="1"
+                    android:paddingLeft="8dp"
+                    android:paddingRight="8dp"
+                    android:paddingTop="4dp"
+                    android:text="@{repo.name}"
+                    android:textColor="#222"
+                    android:textSize="18sp"/>
+
+                <TextView
+                    android:id="@+id/description"
+                    android:layout_width="match_parent"
+                    android:layout_height="wrap_content"
+                    android:layout_below="@id/name"
+                    android:lines="1"
+                    android:maxLines="1"
+                    android:paddingLeft="8dp"
+                    android:paddingRight="8dp"
+                    android:paddingTop="4dp"
+                    android:text="@{repo.description}"
+                    android:textColor="#444"
+                    android:textSize="14sp"/>
+
+                <TextView
+                    android:id="@+id/created"
+                    android:layout_width="wrap_content"
+                    android:layout_height="wrap_content"
+                    android:layout_alignParentBottom="true"
+                    android:lines="1"
+                    android:maxLines="1"
+                    android:paddingBottom="8dp"
+                    android:paddingLeft="8dp"
+                    android:textColor="#444"
+                    android:textSize="14sp"
+                    app:jsonDate="@{repo.created_at}"/>
+
+                <TextView
+                    android:id="@+id/forked"
+                    android:layout_width="wrap_content"
+                    android:layout_height="wrap_content"
+                    android:layout_alignParentBottom="true"
+                    android:layout_alignParentRight="true"
+                    android:drawableLeft="@drawable/ic_content_copy_black_18dp"
+                    android:lines="1"
+                    android:maxLines="1"
+                    android:paddingBottom="8dp"
+                    android:paddingLeft="6dp"
+                    android:paddingRight="10dp"
+                    android:text="@{Integer.toString(repo.forks_count)}"
+                    android:textColor="#444"
+                    android:textSize="14sp"/>
+
+                <TextView
+                    android:id="@+id/starred"
+                    android:layout_width="wrap_content"
+                    android:layout_height="wrap_content"
+                    android:layout_alignParentBottom="true"
+                    android:layout_toLeftOf="@id/forked"
+                    android:drawableLeft="@drawable/ic_star_black_18dp"
+                    android:lines="1"
+                    android:maxLines="1"
+                    android:paddingBottom="8dp"
+                    android:paddingLeft="6dp"
+                    android:paddingRight="6dp"
+                    android:text="@{Integer.toString(repo.stargazers_count)}"
+                    android:textColor="#444"
+                    android:textSize="14sp"/>
+
+                <TextView
+                    android:id="@+id/bugs"
+                    android:layout_width="wrap_content"
+                    android:layout_height="wrap_content"
+                    android:layout_alignParentBottom="true"
+                    android:layout_toLeftOf="@id/starred"
+                    android:drawableLeft="@drawable/ic_bug_report_black_18dp"
+                    android:lines="1"
+                    android:maxLines="1"
+                    android:paddingBottom="8dp"
+                    android:paddingLeft="6dp"
+                    android:paddingRight="6dp"
+                    android:text="@{Integer.toString(repo.open_issues_count)}"
+                    android:textColor="#444"
+                    android:textSize="14sp"/>
+
+            </RelativeLayout>
+
+        </android.support.v7.widget.CardView>
+
+    </FrameLayout>
+</layout>
diff --git a/samples-flatfoot/GithubBrowser/app/src/main/res/layout/user_row.xml b/samples-flatfoot/GithubBrowser/app/src/main/res/layout/user_row.xml
new file mode 100644
index 0000000..495720c
--- /dev/null
+++ b/samples-flatfoot/GithubBrowser/app/src/main/res/layout/user_row.xml
@@ -0,0 +1,74 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  ~ Copyright (C) 2017 The Android Open Source Project
+  ~
+  ~ Licensed under the Apache License, Version 2.0 (the "License");
+  ~ you may not use this file except in compliance with the License.
+  ~ You may obtain a copy of the License at
+  ~
+  ~      http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing, software
+  ~ distributed under the License is distributed on an "AS IS" BASIS,
+  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  ~ See the License for the specific language governing permissions and
+  ~ limitations under the License.
+  -->
+
+<layout
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:app="http://schemas.android.com/apk/res-auto">
+    <data>
+        <variable name="contributor" type="com.android.sample.githubbrowser.data.ContributorData"/>
+        <variable name="callback" type="com.android.sample.githubbrowser.view.PersonClickCallback"/>
+    </data>
+
+    <RelativeLayout
+        android:layout_width="match_parent"
+        android:layout_height="64dp"
+        android:onClick="@{() -> callback.onClick(contributor)}">
+
+        <ImageView
+            android:id="@+id/avatar"
+            android:layout_width="64dp"
+            android:layout_height="64dp"
+            app:imageUrl="@{contributor.avatar_url}"/>
+
+        <View
+            android:id="@+id/separator"
+            android:layout_width="match_parent"
+            android:layout_height="1px"
+            android:layout_toRightOf="@id/avatar"
+            android:background="#80222222"/>
+
+        <TextView
+            android:id="@+id/login"
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:layout_toRightOf="@id/avatar"
+            android:lines="1"
+            android:maxLines="1"
+            android:paddingLeft="8dp"
+            android:paddingRight="8dp"
+            android:paddingTop="4dp"
+            android:text="@{contributor.login}"
+            android:textColor="#222"
+            android:textSize="16sp"/>
+
+        <TextView
+            android:id="@+id/contributions"
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:layout_alignParentBottom="true"
+            android:layout_toRightOf="@id/avatar"
+            android:lines="1"
+            android:maxLines="1"
+            android:paddingBottom="6dp"
+            android:paddingLeft="8dp"
+            android:paddingRight="8dp"
+            android:text="@{@plurals/contributions(contributor.contributions, contributor.contributions)}"
+            android:textColor="#444"
+            android:textSize="14sp"/>
+
+    </RelativeLayout>
+</layout>
diff --git a/samples-flatfoot/GithubBrowser/app/src/main/res/mipmap-hdpi/ic_launcher.png b/samples-flatfoot/GithubBrowser/app/src/main/res/mipmap-hdpi/ic_launcher.png
new file mode 100644
index 0000000..cde69bc
--- /dev/null
+++ b/samples-flatfoot/GithubBrowser/app/src/main/res/mipmap-hdpi/ic_launcher.png
Binary files differ
diff --git a/samples-flatfoot/GithubBrowser/app/src/main/res/mipmap-mdpi/ic_launcher.png b/samples-flatfoot/GithubBrowser/app/src/main/res/mipmap-mdpi/ic_launcher.png
new file mode 100644
index 0000000..c133a0c
--- /dev/null
+++ b/samples-flatfoot/GithubBrowser/app/src/main/res/mipmap-mdpi/ic_launcher.png
Binary files differ
diff --git a/samples-flatfoot/GithubBrowser/app/src/main/res/mipmap-xhdpi/ic_launcher.png b/samples-flatfoot/GithubBrowser/app/src/main/res/mipmap-xhdpi/ic_launcher.png
new file mode 100644
index 0000000..bfa42f0
--- /dev/null
+++ b/samples-flatfoot/GithubBrowser/app/src/main/res/mipmap-xhdpi/ic_launcher.png
Binary files differ
diff --git a/samples-flatfoot/GithubBrowser/app/src/main/res/mipmap-xxhdpi/ic_launcher.png b/samples-flatfoot/GithubBrowser/app/src/main/res/mipmap-xxhdpi/ic_launcher.png
new file mode 100644
index 0000000..324e72c
--- /dev/null
+++ b/samples-flatfoot/GithubBrowser/app/src/main/res/mipmap-xxhdpi/ic_launcher.png
Binary files differ
diff --git a/samples-flatfoot/GithubBrowser/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png b/samples-flatfoot/GithubBrowser/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png
new file mode 100644
index 0000000..aee44e1
--- /dev/null
+++ b/samples-flatfoot/GithubBrowser/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png
Binary files differ
diff --git a/samples-flatfoot/GithubBrowser/app/src/main/res/values-land/ints.xml b/samples-flatfoot/GithubBrowser/app/src/main/res/values-land/ints.xml
new file mode 100644
index 0000000..5b3006e
--- /dev/null
+++ b/samples-flatfoot/GithubBrowser/app/src/main/res/values-land/ints.xml
@@ -0,0 +1,20 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  ~ Copyright (C) 2017 The Android Open Source Project
+  ~
+  ~ Licensed under the Apache License, Version 2.0 (the "License");
+  ~ you may not use this file except in compliance with the License.
+  ~ You may obtain a copy of the License at
+  ~
+  ~      http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing, software
+  ~ distributed under the License is distributed on an "AS IS" BASIS,
+  ~ WITHOUT 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>
+    <integer name="column_count">2</integer>
+</resources>
\ No newline at end of file
diff --git a/samples-flatfoot/GithubBrowser/app/src/main/res/values-v21/styles.xml b/samples-flatfoot/GithubBrowser/app/src/main/res/values-v21/styles.xml
new file mode 100644
index 0000000..064fa76
--- /dev/null
+++ b/samples-flatfoot/GithubBrowser/app/src/main/res/values-v21/styles.xml
@@ -0,0 +1,24 @@
+<!--
+  ~ Copyright (C) 2017 The Android Open Source Project
+  ~
+  ~ Licensed under the Apache License, Version 2.0 (the "License");
+  ~ you may not use this file except in compliance with the License.
+  ~ You may obtain a copy of the License at
+  ~
+  ~      http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing, software
+  ~ distributed under the License is distributed on an "AS IS" BASIS,
+  ~ WITHOUT 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.NoActionBar">
+        <item name="windowActionBar">false</item>
+        <item name="windowNoTitle">true</item>
+        <item name="android:windowDrawsSystemBarBackgrounds">true</item>
+        <item name="android:statusBarColor">@android:color/transparent</item>
+    </style>
+</resources>
diff --git a/samples-flatfoot/GithubBrowser/app/src/main/res/values-w600dp/ints.xml b/samples-flatfoot/GithubBrowser/app/src/main/res/values-w600dp/ints.xml
new file mode 100644
index 0000000..5b3006e
--- /dev/null
+++ b/samples-flatfoot/GithubBrowser/app/src/main/res/values-w600dp/ints.xml
@@ -0,0 +1,20 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  ~ Copyright (C) 2017 The Android Open Source Project
+  ~
+  ~ Licensed under the Apache License, Version 2.0 (the "License");
+  ~ you may not use this file except in compliance with the License.
+  ~ You may obtain a copy of the License at
+  ~
+  ~      http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing, software
+  ~ distributed under the License is distributed on an "AS IS" BASIS,
+  ~ WITHOUT 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>
+    <integer name="column_count">2</integer>
+</resources>
\ No newline at end of file
diff --git a/samples-flatfoot/GithubBrowser/app/src/main/res/values-w820dp/dimens.xml b/samples-flatfoot/GithubBrowser/app/src/main/res/values-w820dp/dimens.xml
new file mode 100644
index 0000000..73f6d98
--- /dev/null
+++ b/samples-flatfoot/GithubBrowser/app/src/main/res/values-w820dp/dimens.xml
@@ -0,0 +1,22 @@
+<!--
+  ~ Copyright (C) 2017 The Android Open Source Project
+  ~
+  ~ Licensed under the Apache License, Version 2.0 (the "License");
+  ~ you may not use this file except in compliance with the License.
+  ~ You may obtain a copy of the License at
+  ~
+  ~      http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing, software
+  ~ distributed under the License is distributed on an "AS IS" BASIS,
+  ~ WITHOUT 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>
+    <!-- Example customization of dimensions originally defined in res/values/dimens.xml
+         (such as screen margins) for screens with more than 820dp of available width. This
+         would include 7" and 10" devices in landscape (~960dp and ~1280dp respectively). -->
+    <dimen name="activity_horizontal_margin">64dp</dimen>
+</resources>
diff --git a/samples-flatfoot/GithubBrowser/app/src/main/res/values-w820dp/ints.xml b/samples-flatfoot/GithubBrowser/app/src/main/res/values-w820dp/ints.xml
new file mode 100644
index 0000000..574f711
--- /dev/null
+++ b/samples-flatfoot/GithubBrowser/app/src/main/res/values-w820dp/ints.xml
@@ -0,0 +1,20 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  ~ Copyright (C) 2017 The Android Open Source Project
+  ~
+  ~ Licensed under the Apache License, Version 2.0 (the "License");
+  ~ you may not use this file except in compliance with the License.
+  ~ You may obtain a copy of the License at
+  ~
+  ~      http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing, software
+  ~ distributed under the License is distributed on an "AS IS" BASIS,
+  ~ WITHOUT 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>
+    <integer name="column_count">3</integer>
+</resources>
\ No newline at end of file
diff --git a/samples-flatfoot/GithubBrowser/app/src/main/res/values/colors.xml b/samples-flatfoot/GithubBrowser/app/src/main/res/values/colors.xml
new file mode 100644
index 0000000..c375001
--- /dev/null
+++ b/samples-flatfoot/GithubBrowser/app/src/main/res/values/colors.xml
@@ -0,0 +1,23 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  ~ Copyright (C) 2017 The Android Open Source Project
+  ~
+  ~ Licensed under the Apache License, Version 2.0 (the "License");
+  ~ you may not use this file except in compliance with the License.
+  ~ You may obtain a copy of the License at
+  ~
+  ~      http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing, software
+  ~ distributed under the License is distributed on an "AS IS" BASIS,
+  ~ WITHOUT 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>
+    <color name="colorPrimary">#e91e63</color>
+    <color name="colorPrimaryDark">#d81b60</color>
+    <color name="colorAccent">#4caf50</color>
+    <color name="background">#eeeeee</color>
+</resources>
diff --git a/samples-flatfoot/GithubBrowser/app/src/main/res/values/dimens.xml b/samples-flatfoot/GithubBrowser/app/src/main/res/values/dimens.xml
new file mode 100644
index 0000000..87c9565
--- /dev/null
+++ b/samples-flatfoot/GithubBrowser/app/src/main/res/values/dimens.xml
@@ -0,0 +1,22 @@
+<!--
+  ~ Copyright (C) 2017 The Android Open Source Project
+  ~
+  ~ Licensed under the Apache License, Version 2.0 (the "License");
+  ~ you may not use this file except in compliance with the License.
+  ~ You may obtain a copy of the License at
+  ~
+  ~      http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing, software
+  ~ distributed under the License is distributed on an "AS IS" BASIS,
+  ~ WITHOUT 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="fab_margin">16dp</dimen>
+</resources>
diff --git a/samples-flatfoot/GithubBrowser/app/src/main/res/values/ints.xml b/samples-flatfoot/GithubBrowser/app/src/main/res/values/ints.xml
new file mode 100644
index 0000000..e489869
--- /dev/null
+++ b/samples-flatfoot/GithubBrowser/app/src/main/res/values/ints.xml
@@ -0,0 +1,20 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  ~ Copyright (C) 2017 The Android Open Source Project
+  ~
+  ~ Licensed under the Apache License, Version 2.0 (the "License");
+  ~ you may not use this file except in compliance with the License.
+  ~ You may obtain a copy of the License at
+  ~
+  ~      http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing, software
+  ~ distributed under the License is distributed on an "AS IS" BASIS,
+  ~ WITHOUT 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>
+    <integer name="column_count">1</integer>
+</resources>
\ No newline at end of file
diff --git a/samples-flatfoot/GithubBrowser/app/src/main/res/values/strings.xml b/samples-flatfoot/GithubBrowser/app/src/main/res/values/strings.xml
new file mode 100644
index 0000000..a77f5ef
--- /dev/null
+++ b/samples-flatfoot/GithubBrowser/app/src/main/res/values/strings.xml
@@ -0,0 +1,45 @@
+<!--
+  ~ Copyright (C) 2017 The Android Open Source Project
+  ~
+  ~ Licensed under the Apache License, Version 2.0 (the "License");
+  ~ you may not use this file except in compliance with the License.
+  ~ You may obtain a copy of the License at
+  ~
+  ~      http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing, software
+  ~ distributed under the License is distributed on an "AS IS" BASIS,
+  ~ WITHOUT 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">GithubBrowser</string>
+    <string name="ok">OK</string>
+    <string name="cancel">Cancel</string>
+
+    <string name="auth_token_title">Type or paste your GitHub token</string>
+    <string name="hint_token">GitHub token</string>
+    <string name="contributors">Contributors</string>
+    <plurals name="bugs">
+        <item quantity="one">%1$d issue</item>
+        <item quantity="other">%1$d issues</item>
+    </plurals>
+    <string name="starred">%1$d starred</string>
+    <string name="forked">%1$d forked</string>
+    <plurals name="contributions">
+        <item quantity="one">%1$d contribution</item>
+        <item quantity="other">%1$d contributions</item>
+    </plurals>
+    <string name="created">Created on %1$s</string>
+    <string name="repositories">Repositories</string>
+    <string name="followers">Followers</string>
+    <string name="following">Following</string>
+    <string name="joined">Joined on %1$s</string>
+    <string name="hint_email">Email</string>
+    <string name="hint_location">Location</string>
+
+    <string name="no_results">No data for \'%1$s\'</string>
+    <string name="load_error">Couldn\'t load data for \'%1$s\'</string>
+</resources>
diff --git a/samples-flatfoot/GithubBrowser/app/src/main/res/values/styles.xml b/samples-flatfoot/GithubBrowser/app/src/main/res/values/styles.xml
new file mode 100644
index 0000000..4b769cc
--- /dev/null
+++ b/samples-flatfoot/GithubBrowser/app/src/main/res/values/styles.xml
@@ -0,0 +1,33 @@
+<!--
+  ~ Copyright (C) 2017 The Android Open Source Project
+  ~
+  ~ Licensed under the Apache License, Version 2.0 (the "License");
+  ~ you may not use this file except in compliance with the License.
+  ~ You may obtain a copy of the License at
+  ~
+  ~      http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing, software
+  ~ distributed under the License is distributed on an "AS IS" BASIS,
+  ~ WITHOUT 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. -->
+    <style name="AppTheme" parent="Theme.AppCompat.Light.DarkActionBar">
+        <!-- Customize your theme here. -->
+        <item name="colorPrimary">@color/colorPrimary</item>
+        <item name="colorPrimaryDark">@color/colorPrimaryDark</item>
+        <item name="colorAccent">@color/colorAccent</item>
+    </style>
+    <style name="AppTheme.NoActionBar">
+        <item name="windowActionBar">false</item>
+        <item name="windowNoTitle">true</item>
+    </style>
+    <style name="AppTheme.AppBarOverlay" parent="ThemeOverlay.AppCompat.Dark.ActionBar"/>
+    <style name="AppTheme.PopupOverlay" parent="ThemeOverlay.AppCompat.Light"/>
+
+</resources>
diff --git a/samples-flatfoot/GithubBrowser/build.gradle b/samples-flatfoot/GithubBrowser/build.gradle
new file mode 100644
index 0000000..99ae156
--- /dev/null
+++ b/samples-flatfoot/GithubBrowser/build.gradle
@@ -0,0 +1,44 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT 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 build file where you can add configuration options common to all sub-projects/modules.
+
+buildscript {
+    repositories {
+        jcenter()
+    }
+    dependencies {
+        classpath 'com.android.tools.build:gradle:2.3.0'
+
+        // NOTE: Do not place your application dependencies here; they belong
+        // in the individual module build.gradle files
+    }
+}
+
+ext.flatfootVersion = "1.0-SNAPSHOT"
+ext.supportLibVersion = "25.2.0"
+ext.daggerVersion = "2.7"
+
+allprojects { p ->
+    repositories {
+        jcenter()
+        maven { url '/Volumes/ssd/src/ff-repo/m2repository' }
+    }
+}
+
+task clean(type: Delete) {
+    delete rootProject.buildDir
+}
diff --git a/samples-flatfoot/GithubBrowser/gradle.properties b/samples-flatfoot/GithubBrowser/gradle.properties
new file mode 100644
index 0000000..be27115
--- /dev/null
+++ b/samples-flatfoot/GithubBrowser/gradle.properties
@@ -0,0 +1,33 @@
+#
+# Copyright (C) 2017 The Android Open Source Project
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#      http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+
+# Project-wide Gradle settings.
+
+# IDE (e.g. Android Studio) users:
+# Gradle settings configured through the IDE *will override*
+# any settings specified in this file.
+
+# For more details on how to configure your build environment visit
+# http://www.gradle.org/docs/current/userguide/build_environment.html
+
+# Specifies the JVM arguments used for the daemon process.
+# The setting is particularly useful for tweaking memory settings.
+org.gradle.jvmargs=-Xmx1536m
+
+# When configured, Gradle will run in incubating parallel mode.
+# This option should only be used with decoupled projects. More details, visit
+# http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects
+# org.gradle.parallel=true
diff --git a/samples-flatfoot/GithubBrowser/gradle/wrapper/gradle-wrapper.jar b/samples-flatfoot/GithubBrowser/gradle/wrapper/gradle-wrapper.jar
new file mode 100644
index 0000000..13372ae
--- /dev/null
+++ b/samples-flatfoot/GithubBrowser/gradle/wrapper/gradle-wrapper.jar
Binary files differ
diff --git a/samples-flatfoot/GithubBrowser/gradle/wrapper/gradle-wrapper.properties b/samples-flatfoot/GithubBrowser/gradle/wrapper/gradle-wrapper.properties
new file mode 100644
index 0000000..de8435f
--- /dev/null
+++ b/samples-flatfoot/GithubBrowser/gradle/wrapper/gradle-wrapper.properties
@@ -0,0 +1,22 @@
+#
+# Copyright (C) 2017 The Android Open Source Project
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#      http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+
+#Mon Mar 13 21:50:29 PDT 2017
+distributionBase=GRADLE_USER_HOME
+distributionPath=wrapper/dists
+zipStoreBase=GRADLE_USER_HOME
+zipStorePath=wrapper/dists
+distributionUrl=https\://services.gradle.org/distributions/gradle-3.3-all.zip
diff --git a/samples-flatfoot/GithubBrowser/gradlew b/samples-flatfoot/GithubBrowser/gradlew
new file mode 100755
index 0000000..9d82f78
--- /dev/null
+++ b/samples-flatfoot/GithubBrowser/gradlew
@@ -0,0 +1,160 @@
+#!/usr/bin/env bash
+
+##############################################################################
+##
+##  Gradle start up script for UN*X
+##
+##############################################################################
+
+# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
+DEFAULT_JVM_OPTS=""
+
+APP_NAME="Gradle"
+APP_BASE_NAME=`basename "$0"`
+
+# Use the maximum available, or set MAX_FD != -1 to use that value.
+MAX_FD="maximum"
+
+warn ( ) {
+    echo "$*"
+}
+
+die ( ) {
+    echo
+    echo "$*"
+    echo
+    exit 1
+}
+
+# OS specific support (must be 'true' or 'false').
+cygwin=false
+msys=false
+darwin=false
+case "`uname`" in
+  CYGWIN* )
+    cygwin=true
+    ;;
+  Darwin* )
+    darwin=true
+    ;;
+  MINGW* )
+    msys=true
+    ;;
+esac
+
+# Attempt to set APP_HOME
+# Resolve links: $0 may be a link
+PRG="$0"
+# Need this for relative symlinks.
+while [ -h "$PRG" ] ; do
+    ls=`ls -ld "$PRG"`
+    link=`expr "$ls" : '.*-> \(.*\)$'`
+    if expr "$link" : '/.*' > /dev/null; then
+        PRG="$link"
+    else
+        PRG=`dirname "$PRG"`"/$link"
+    fi
+done
+SAVED="`pwd`"
+cd "`dirname \"$PRG\"`/" >/dev/null
+APP_HOME="`pwd -P`"
+cd "$SAVED" >/dev/null
+
+CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
+
+# Determine the Java command to use to start the JVM.
+if [ -n "$JAVA_HOME" ] ; then
+    if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
+        # IBM's JDK on AIX uses strange locations for the executables
+        JAVACMD="$JAVA_HOME/jre/sh/java"
+    else
+        JAVACMD="$JAVA_HOME/bin/java"
+    fi
+    if [ ! -x "$JAVACMD" ] ; then
+        die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
+
+Please set the JAVA_HOME variable in your environment to match the
+location of your Java installation."
+    fi
+else
+    JAVACMD="java"
+    which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
+
+Please set the JAVA_HOME variable in your environment to match the
+location of your Java installation."
+fi
+
+# Increase the maximum file descriptors if we can.
+if [ "$cygwin" = "false" -a "$darwin" = "false" ] ; then
+    MAX_FD_LIMIT=`ulimit -H -n`
+    if [ $? -eq 0 ] ; then
+        if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then
+            MAX_FD="$MAX_FD_LIMIT"
+        fi
+        ulimit -n $MAX_FD
+        if [ $? -ne 0 ] ; then
+            warn "Could not set maximum file descriptor limit: $MAX_FD"
+        fi
+    else
+        warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT"
+    fi
+fi
+
+# For Darwin, add options to specify how the application appears in the dock
+if $darwin; then
+    GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\""
+fi
+
+# For Cygwin, switch paths to Windows format before running java
+if $cygwin ; then
+    APP_HOME=`cygpath --path --mixed "$APP_HOME"`
+    CLASSPATH=`cygpath --path --mixed "$CLASSPATH"`
+    JAVACMD=`cygpath --unix "$JAVACMD"`
+
+    # We build the pattern for arguments to be converted via cygpath
+    ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null`
+    SEP=""
+    for dir in $ROOTDIRSRAW ; do
+        ROOTDIRS="$ROOTDIRS$SEP$dir"
+        SEP="|"
+    done
+    OURCYGPATTERN="(^($ROOTDIRS))"
+    # Add a user-defined pattern to the cygpath arguments
+    if [ "$GRADLE_CYGPATTERN" != "" ] ; then
+        OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)"
+    fi
+    # Now convert the arguments - kludge to limit ourselves to /bin/sh
+    i=0
+    for arg in "$@" ; do
+        CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -`
+        CHECK2=`echo "$arg"|egrep -c "^-"`                                 ### Determine if an option
+
+        if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then                    ### Added a condition
+            eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"`
+        else
+            eval `echo args$i`="\"$arg\""
+        fi
+        i=$((i+1))
+    done
+    case $i in
+        (0) set -- ;;
+        (1) set -- "$args0" ;;
+        (2) set -- "$args0" "$args1" ;;
+        (3) set -- "$args0" "$args1" "$args2" ;;
+        (4) set -- "$args0" "$args1" "$args2" "$args3" ;;
+        (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;;
+        (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;;
+        (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;;
+        (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;;
+        (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;;
+    esac
+fi
+
+# Split up the JVM_OPTS And GRADLE_OPTS values into an array, following the shell quoting and substitution rules
+function splitJvmOpts() {
+    JVM_OPTS=("$@")
+}
+eval splitJvmOpts $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS
+JVM_OPTS[${#JVM_OPTS[*]}]="-Dorg.gradle.appname=$APP_BASE_NAME"
+
+exec "$JAVACMD" "${JVM_OPTS[@]}" -classpath "$CLASSPATH" org.gradle.wrapper.GradleWrapperMain "$@"
diff --git a/samples-flatfoot/GithubBrowser/gradlew.bat b/samples-flatfoot/GithubBrowser/gradlew.bat
new file mode 100644
index 0000000..aec9973
--- /dev/null
+++ b/samples-flatfoot/GithubBrowser/gradlew.bat
@@ -0,0 +1,90 @@
+@if "%DEBUG%" == "" @echo off

+@rem ##########################################################################

+@rem

+@rem  Gradle startup script for Windows

+@rem

+@rem ##########################################################################

+

+@rem Set local scope for the variables with windows NT shell

+if "%OS%"=="Windows_NT" setlocal

+

+@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.

+set DEFAULT_JVM_OPTS=

+

+set DIRNAME=%~dp0

+if "%DIRNAME%" == "" set DIRNAME=.

+set APP_BASE_NAME=%~n0

+set APP_HOME=%DIRNAME%

+

+@rem Find java.exe

+if defined JAVA_HOME goto findJavaFromJavaHome

+

+set JAVA_EXE=java.exe

+%JAVA_EXE% -version >NUL 2>&1

+if "%ERRORLEVEL%" == "0" goto init

+

+echo.

+echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.

+echo.

+echo Please set the JAVA_HOME variable in your environment to match the

+echo location of your Java installation.

+

+goto fail

+

+:findJavaFromJavaHome

+set JAVA_HOME=%JAVA_HOME:"=%

+set JAVA_EXE=%JAVA_HOME%/bin/java.exe

+

+if exist "%JAVA_EXE%" goto init

+

+echo.

+echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%

+echo.

+echo Please set the JAVA_HOME variable in your environment to match the

+echo location of your Java installation.

+

+goto fail

+

+:init

+@rem Get command-line arguments, handling Windowz variants

+

+if not "%OS%" == "Windows_NT" goto win9xME_args

+if "%@eval[2+2]" == "4" goto 4NT_args

+

+:win9xME_args

+@rem Slurp the command line arguments.

+set CMD_LINE_ARGS=

+set _SKIP=2

+

+:win9xME_args_slurp

+if "x%~1" == "x" goto execute

+

+set CMD_LINE_ARGS=%*

+goto execute

+

+:4NT_args

+@rem Get arguments from the 4NT Shell from JP Software

+set CMD_LINE_ARGS=%$

+

+:execute

+@rem Setup the command line

+

+set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar

+

+@rem Execute Gradle

+"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS%

+

+:end

+@rem End local scope for the variables with windows NT shell

+if "%ERRORLEVEL%"=="0" goto mainEnd

+

+:fail

+rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of

+rem the _cmd.exe /c_ return code!

+if  not "" == "%GRADLE_EXIT_CONSOLE%" exit 1

+exit /b 1

+

+:mainEnd

+if "%OS%"=="Windows_NT" endlocal

+

+:omega

diff --git a/samples-flatfoot/GithubBrowser/settings.gradle b/samples-flatfoot/GithubBrowser/settings.gradle
new file mode 100644
index 0000000..1df87d4
--- /dev/null
+++ b/samples-flatfoot/GithubBrowser/settings.gradle
@@ -0,0 +1,17 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+include ':app'
diff --git a/samples-flatfoot/MusicPlayer/.gitignore b/samples-flatfoot/MusicPlayer/.gitignore
new file mode 100644
index 0000000..39fb081
--- /dev/null
+++ b/samples-flatfoot/MusicPlayer/.gitignore
@@ -0,0 +1,9 @@
+*.iml
+.gradle
+/local.properties
+/.idea/workspace.xml
+/.idea/libraries
+.DS_Store
+/build
+/captures
+.externalNativeBuild
diff --git a/samples-flatfoot/MusicPlayer/app/.gitignore b/samples-flatfoot/MusicPlayer/app/.gitignore
new file mode 100644
index 0000000..796b96d
--- /dev/null
+++ b/samples-flatfoot/MusicPlayer/app/.gitignore
@@ -0,0 +1 @@
+/build
diff --git a/samples-flatfoot/MusicPlayer/app/build.gradle b/samples-flatfoot/MusicPlayer/app/build.gradle
new file mode 100644
index 0000000..58a68a1
--- /dev/null
+++ b/samples-flatfoot/MusicPlayer/app/build.gradle
@@ -0,0 +1,38 @@
+apply plugin: 'com.android.application'
+
+android {
+    compileSdkVersion 25
+    buildToolsVersion "25.0.2"
+    defaultConfig {
+        applicationId "com.android.sample.musicplayer"
+        minSdkVersion 14
+        targetSdkVersion 25
+        versionCode 1
+        versionName "1.0"
+        testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
+    }
+    dataBinding {
+        enabled = true
+    }
+    buildTypes {
+        release {
+            minifyEnabled false
+            proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
+        }
+    }
+}
+
+dependencies {
+    compile fileTree(dir: 'libs', include: ['*.jar'])
+    androidTestCompile('com.android.support.test.espresso:espresso-core:2.2.2', {
+        exclude group: 'com.android.support', module: 'support-annotations'
+    })
+    compile "com.android.support:appcompat-v7:$supportLibVersion"
+    compile "com.android.support:design:$supportLibVersion"
+    compile "com.android.support.lifecycle:runtime:$flatfootVersion"
+    compile "com.android.support.lifecycle:extensions:$flatfootVersion"
+
+    annotationProcessor "com.android.support.lifecycle:compiler:$flatfootVersion"
+
+    testCompile 'junit:junit:4.12'
+}
diff --git a/samples-flatfoot/MusicPlayer/app/proguard-rules.pro b/samples-flatfoot/MusicPlayer/app/proguard-rules.pro
new file mode 100644
index 0000000..3916ad7
--- /dev/null
+++ b/samples-flatfoot/MusicPlayer/app/proguard-rules.pro
@@ -0,0 +1,17 @@
+# Add project specific ProGuard rules here.
+# By default, the flags in this file are appended to flags specified
+# in /Users/kirillg/Android/sdk/tools/proguard/proguard-android.txt
+# You can edit the include path and order by changing the proguardFiles
+# directive in build.gradle.
+#
+# For more details, see
+#   http://developer.android.com/guide/developing/tools/proguard.html
+
+# Add any project specific keep options here:
+
+# If your project uses WebView with JS, uncomment the following
+# and specify the fully qualified class name to the JavaScript interface
+# class:
+#-keepclassmembers class fqcn.of.javascript.interface.for.webview {
+#   public *;
+#}
diff --git a/samples-flatfoot/MusicPlayer/app/src/main/AndroidManifest.xml b/samples-flatfoot/MusicPlayer/app/src/main/AndroidManifest.xml
new file mode 100644
index 0000000..d96b452
--- /dev/null
+++ b/samples-flatfoot/MusicPlayer/app/src/main/AndroidManifest.xml
@@ -0,0 +1,36 @@
+<?xml version="1.0" encoding="utf-8"?>
+<manifest package="com.android.sample.musicplayer"
+          xmlns:android="http://schemas.android.com/apk/res/android">
+    <uses-permission android:name="android.permission.WAKE_LOCK" />
+    <permission android:name="android.permission.MEDIA_CONTENT_CONTROL" />
+
+    <application
+        android:allowBackup="true"
+        android:icon="@mipmap/ic_launcher"
+        android:label="@string/app_name"
+        android:supportsRtl="true"
+        android:theme="@style/AppTheme">
+        <activity
+            android:name=".MainActivity"
+            android:label="@string/app_name"
+            android:theme="@style/AppTheme.NoActionBar">
+            <intent-filter>
+                <action android:name="android.intent.action.MAIN"/>
+
+                <category android:name="android.intent.category.LAUNCHER"/>
+            </intent-filter>
+        </activity>
+
+        <service android:exported="false" android:name=".MusicService">
+            <intent-filter>
+                <action android:name="com.android.sample.musicplayer.action.START" />
+                <action android:name="com.android.sample.musicplayer.action.PLAY" />
+                <action android:name="com.android.sample.musicplayer.action.PAUSE" />
+                <action android:name="com.android.sample.musicplayer.action.NEXT" />
+                <action android:name="com.android.sample.musicplayer.action.PREV" />
+                <action android:name="com.android.sample.musicplayer.action.STOP" />
+            </intent-filter>
+        </service>
+    </application>
+
+</manifest>
\ No newline at end of file
diff --git a/samples-flatfoot/MusicPlayer/app/src/main/java/com/android/sample/musicplayer/BaseActivity.java b/samples-flatfoot/MusicPlayer/app/src/main/java/com/android/sample/musicplayer/BaseActivity.java
new file mode 100644
index 0000000..2ca54a9
--- /dev/null
+++ b/samples-flatfoot/MusicPlayer/app/src/main/java/com/android/sample/musicplayer/BaseActivity.java
@@ -0,0 +1,62 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.sample.musicplayer;
+
+import android.os.Bundle;
+import android.support.v7.app.AppCompatActivity;
+
+import com.android.support.lifecycle.ActivityLifecycleDispatcher;
+import com.android.support.lifecycle.Lifecycle;
+import com.android.support.lifecycle.LifecycleProvider;
+
+/**
+ * Temporary base activity that acts as lifecycle provider.
+ */
+public abstract class BaseActivity extends AppCompatActivity implements LifecycleProvider {
+    private final ActivityLifecycleDispatcher mDispatcher = new ActivityLifecycleDispatcher(this,
+            this);
+
+    @Override
+    protected void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+        mDispatcher.onActivityPostSuperOnCreate();
+    }
+    @Override
+    protected void onResume() {
+        mDispatcher.onActivityPreSuperOnResume();
+        super.onResume();
+    }
+    @Override
+    protected void onPause() {
+        mDispatcher.onActivityPreSuperOnPause();
+        super.onPause();
+    }
+    @Override
+    protected void onStop() {
+        mDispatcher.onActivityPreSuperOnStop();
+        super.onStop();
+    }
+    @Override
+    protected void onDestroy() {
+        mDispatcher.onActivityPreSuperOnDestroy();
+        super.onDestroy();
+    }
+
+    @Override
+    public Lifecycle getLifecycle() {
+        return mDispatcher.getLifecycle();
+    }
+}
diff --git a/samples-flatfoot/MusicPlayer/app/src/main/java/com/android/sample/musicplayer/MainActivity.java b/samples-flatfoot/MusicPlayer/app/src/main/java/com/android/sample/musicplayer/MainActivity.java
new file mode 100644
index 0000000..e07610a
--- /dev/null
+++ b/samples-flatfoot/MusicPlayer/app/src/main/java/com/android/sample/musicplayer/MainActivity.java
@@ -0,0 +1,113 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.sample.musicplayer;
+
+import android.content.Intent;
+import android.os.Bundle;
+import android.support.annotation.Nullable;
+import android.support.design.widget.FloatingActionButton;
+import android.support.v7.widget.LinearLayoutManager;
+import android.support.v7.widget.RecyclerView;
+import android.support.v7.widget.Toolbar;
+import android.view.View;
+
+import com.android.sample.musicplayer.MusicRepository.TrackMetadata;
+import com.android.sample.musicplayer.adapter.MusicTrackListAdapter;
+import com.android.support.lifecycle.LiveData;
+import com.android.support.lifecycle.Observer;
+
+import java.util.List;
+
+/**
+ * Our main activity.
+ */
+public class MainActivity extends BaseActivity {
+    private RecyclerView mRecyclerView;
+
+    private MusicTrackListAdapter mMusicTrackListAdapter;
+    private int mCurrPlaybackState;
+
+    private void updateFab() {
+        final FloatingActionButton fab = (FloatingActionButton) findViewById(R.id.fab);
+        switch (mCurrPlaybackState) {
+            case MusicRepository.STATE_PLAYING:
+                fab.setImageResource(R.drawable.ic_pause_white_36dp);
+                break;
+            case MusicRepository.STATE_PAUSED:
+            case MusicRepository.STATE_STOPPED:
+                fab.setImageResource(R.drawable.ic_play_arrow_white_36dp);
+                break;
+        }
+    }
+
+    @Override
+    protected void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+        setContentView(R.layout.activity_main);
+        Toolbar toolbar = (Toolbar) findViewById(R.id.toolbar);
+        setSupportActionBar(toolbar);
+
+        // Start the service. From this point on there is no direct communication between the
+        // activity and the service. Everything is done by updating LiveData objects in the
+        // repository and observing / reacting to those changes.
+        startService(new Intent(MusicService.ACTION_START).setPackage(
+                "com.android.sample.musicplayer"));
+
+        final MusicRepository musicRepository = MusicRepository.getInstance();
+        LiveData<Integer> currentlyActiveTrackData = musicRepository.getCurrentlyActiveTrackData();
+        currentlyActiveTrackData.observe(this, new Observer<Integer>() {
+            @Override
+            public void onChanged(@Nullable Integer integer) {
+                if (mMusicTrackListAdapter != null) {
+                    mMusicTrackListAdapter.setActiveTrackIndex(integer);
+                }
+            }
+        });
+        LiveData<Integer> stateData = musicRepository.getStateData();
+        stateData.observe(this, new Observer<Integer>() {
+            @Override
+            public void onChanged(@Nullable Integer integer) {
+                mCurrPlaybackState = integer;
+                updateFab();
+            }
+        });
+
+        final FloatingActionButton fab = (FloatingActionButton) findViewById(R.id.fab);
+        updateFab();
+
+        fab.setOnClickListener(new View.OnClickListener() {
+            @Override
+            public void onClick(View view) {
+                if (mCurrPlaybackState == MusicRepository.STATE_INITIAL) {
+                    // If the FAB is clicked in the initial state, start the playback from the
+                    // first track
+                    musicRepository.setTrack(0);
+                } else {
+                    // Otherwise we're past the initial state. Set the state to playing or
+                    // paused based on the current state
+                    musicRepository.setState((mCurrPlaybackState == MusicRepository.STATE_PLAYING)
+                            ? MusicRepository.STATE_PAUSED : MusicRepository.STATE_PLAYING);
+                }
+            }
+        });
+
+        mRecyclerView = (RecyclerView) findViewById(R.id.recycler);
+        final List<TrackMetadata> tracks = MusicRepository.getInstance().getTracks();
+        mMusicTrackListAdapter = new MusicTrackListAdapter(tracks);
+        mRecyclerView.setAdapter(mMusicTrackListAdapter);
+        mRecyclerView.setLayoutManager(new LinearLayoutManager(this));
+    }
+}
diff --git a/samples-flatfoot/MusicPlayer/app/src/main/java/com/android/sample/musicplayer/MusicRepository.java b/samples-flatfoot/MusicPlayer/app/src/main/java/com/android/sample/musicplayer/MusicRepository.java
new file mode 100644
index 0000000..5fb04a4
--- /dev/null
+++ b/samples-flatfoot/MusicPlayer/app/src/main/java/com/android/sample/musicplayer/MusicRepository.java
@@ -0,0 +1,162 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.sample.musicplayer;
+
+import android.support.annotation.RawRes;
+
+import com.android.support.lifecycle.LiveData;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+
+/**
+ * Music track / state repository.
+ */
+public class MusicRepository {
+    public static final int STATE_INITIAL = 0;
+    public static final int STATE_PLAYING = 1;
+    public static final int STATE_PAUSED = 2;
+    public static final int STATE_PREPARING = 3;
+    public static final int STATE_STOPPED = 4;
+
+    private static MusicRepository sInstance;
+
+    /**
+     * Metadata for a single track.
+     */
+    public static final class TrackMetadata {
+        private final int mIndex;
+        private final String mTitle;
+        private final String mArtist;
+        @RawRes private final int mTrackRes;
+
+        public TrackMetadata(int index, String title, String artist, @RawRes int trackRes) {
+            mIndex = index;
+            mTitle = title;
+            mArtist = artist;
+            mTrackRes = trackRes;
+        }
+
+        public int getIndex() {
+            return mIndex;
+        }
+
+        public String getTitle() {
+            return mTitle;
+        }
+
+        public String getArtist() {
+            return mArtist;
+        }
+
+        @RawRes
+        public int getTrackRes() {
+            return mTrackRes;
+        }
+    }
+
+    private List<TrackMetadata> mTracks;
+
+    private LiveData<Integer> mCurrentlyActiveTrackData;
+    private LiveData<Integer> mStateData;
+
+    /**
+     * Gets the repository instance.
+     */
+    public static synchronized MusicRepository getInstance() {
+        if (sInstance == null) {
+            sInstance = new MusicRepository();
+        }
+        return sInstance;
+    }
+
+    private MusicRepository() {
+        mTracks = new ArrayList<>(9);
+        mTracks.add(new TrackMetadata(1, "Tilt You Better", "Dawn Lentil", R.raw.track1));
+        mTracks.add(new TrackMetadata(2, "Moongirl", "The Weekdy", R.raw.track2));
+        mTracks.add(new TrackMetadata(3, "Further", "The Linkdrinkers", R.raw.track3));
+        mTracks.add(new TrackMetadata(4, "Back and Forth", "Marina Venti", R.raw.track4));
+        mTracks.add(new TrackMetadata(5, "Let Me Hate You", "Juji Beans", R.raw.track5));
+        mTracks.add(new TrackMetadata(6, "Thirsty", "Smiley Leftfield", R.raw.track6));
+        mTracks.add(new TrackMetadata(7, "Cheap Deals", "Skia", R.raw.track7));
+        mTracks.add(new TrackMetadata(8, "Don't Stop the Drilling", "Raw Oilfield", R.raw.track8));
+        mTracks.add(new TrackMetadata(9, "Million Regressions", "Lady BreakBuild", R.raw.track9));
+
+        mCurrentlyActiveTrackData = new LiveData<>();
+        mCurrentlyActiveTrackData.setValue(-1);
+
+        mStateData = new LiveData<>();
+        mStateData.setValue(STATE_INITIAL);
+    }
+
+    /**
+     * Returns the unmodifiable list of tracks in this repository.
+     */
+    public List<TrackMetadata> getTracks() {
+        return Collections.unmodifiableList(mTracks);
+    }
+
+    /**
+     * Goes to the specific track.
+     */
+    public void setTrack(int trackIndex) {
+        mCurrentlyActiveTrackData.setValue(trackIndex);
+    }
+
+    /**
+     * Goes to the next track.
+     */
+    public void goToNextTrack() {
+        int nextSourceIndex = mCurrentlyActiveTrackData.getValue() + 1;
+        if (nextSourceIndex == mTracks.size()) {
+            nextSourceIndex = 0;
+        }
+        setTrack(nextSourceIndex);
+    }
+
+    /**
+     * Goes to the previous track.
+     */
+    public void goToPreviousTrack() {
+        int prevSourceIndex = mCurrentlyActiveTrackData.getValue() - 1;
+        if (prevSourceIndex == -1) {
+            prevSourceIndex = mTracks.size() - 1;
+        }
+        setTrack(prevSourceIndex);
+    }
+
+    /**
+     * Sets the new value for the playback state.
+     */
+    public void setState(int state) {
+        mStateData.setValue(state);
+    }
+
+    /**
+     * Returns the {@link LiveData} object that wraps the currently active track index.
+     */
+    public LiveData<Integer> getCurrentlyActiveTrackData() {
+        return this.mCurrentlyActiveTrackData;
+    }
+
+    /**
+     * Returns the {@link LiveData} object that wraps the playback state.
+     */
+    public LiveData<Integer> getStateData() {
+        return mStateData;
+    }
+}
diff --git a/samples-flatfoot/MusicPlayer/app/src/main/java/com/android/sample/musicplayer/MusicService.java b/samples-flatfoot/MusicPlayer/app/src/main/java/com/android/sample/musicplayer/MusicService.java
new file mode 100644
index 0000000..a9d623c
--- /dev/null
+++ b/samples-flatfoot/MusicPlayer/app/src/main/java/com/android/sample/musicplayer/MusicService.java
@@ -0,0 +1,442 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.sample.musicplayer;
+
+import android.app.PendingIntent;
+import android.content.Intent;
+import android.graphics.Bitmap;
+import android.graphics.BitmapFactory;
+import android.media.MediaPlayer;
+import android.media.MediaPlayer.OnCompletionListener;
+import android.media.MediaPlayer.OnPreparedListener;
+import android.net.Uri;
+import android.os.IBinder;
+import android.os.PowerManager;
+import android.support.annotation.Nullable;
+import android.support.annotation.RawRes;
+import android.support.v4.app.NotificationManagerCompat;
+import android.support.v4.media.MediaMetadataCompat;
+import android.support.v4.media.session.MediaSessionCompat;
+import android.support.v7.app.NotificationCompat;
+
+import com.android.sample.musicplayer.MusicRepository.TrackMetadata;
+import com.android.support.lifecycle.LifecycleService;
+import com.android.support.lifecycle.LiveData;
+import com.android.support.lifecycle.Observer;
+
+import java.io.IOException;
+import java.util.List;
+
+/**
+ * Music playback service.
+ */
+public class MusicService extends LifecycleService implements OnCompletionListener,
+        OnPreparedListener {
+    // Note that only START action is an entry point "exposed" to the rest of the
+    // application. The rest are actions set on the notification intents fired off
+    // by this service itself.
+    public static final String ACTION_START = "com.android.sample.musicplayer.action.START";
+    private static final String ACTION_PLAY = "com.android.sample.musicplayer.action.PLAY";
+    private static final String ACTION_PAUSE = "com.android.sample.musicplayer.action.PAUSE";
+    private static final String ACTION_STOP = "com.android.sample.musicplayer.action.STOP";
+    private static final String ACTION_NEXT = "com.android.sample.musicplayer.action.NEXT";
+    private static final String ACTION_PREV = "com.android.sample.musicplayer.action.PREV";
+
+    private static final String RESOURCE_PREFIX =
+            "android.resource://com.android.sample.musicplayer/";
+
+    // The ID we use for the notification (the onscreen alert that appears at the notification
+    // area at the top of the screen as an icon -- and as text as well if the user expands the
+    // notification area).
+    private static final int NOTIFICATION_ID = 1;
+
+    private MediaSessionCompat mMediaSession;
+
+    private MediaPlayer mMediaPlayer = null;
+    private NotificationManagerCompat mNotificationManager;
+    private NotificationCompat.Builder mNotificationBuilder;
+
+    private MusicRepository mMusicRepository;
+    private int mCurrPlaybackState;
+    private int mCurrActiveTrackIndex;
+    private List<TrackMetadata> mTracks;
+
+    @Override
+    public void onCreate() {
+        super.onCreate();
+
+        mMediaSession = new MediaSessionCompat(this, MusicService.class.getSimpleName());
+        mMediaSession.setFlags(MediaSessionCompat.FLAG_HANDLES_TRANSPORT_CONTROLS);
+        mMediaSession.setActive(true);
+
+        mMusicRepository = MusicRepository.getInstance();
+
+        mTracks = mMusicRepository.getTracks();
+
+        // Attach Callback to receive MediaSession updates
+        mMediaSession.setCallback(new MediaSessionCompat.Callback() {
+            // Implement callbacks
+            @Override
+            public void onPlay() {
+                super.onPlay();
+                processPlayRequest();
+            }
+
+            @Override
+            public void onPause() {
+                super.onPause();
+                processPauseRequest();
+            }
+
+            @Override
+            public void onSkipToNext() {
+                super.onSkipToNext();
+                processNextRequest();
+            }
+
+            @Override
+            public void onSkipToPrevious() {
+                super.onSkipToPrevious();
+                processPreviousRequest();
+            }
+
+            @Override
+            public void onStop() {
+                super.onStop();
+                processStopRequest();
+            }
+        });
+
+        mNotificationManager = NotificationManagerCompat.from(this);
+
+        // Register self as the observer on the LiveData object that wraps the currently
+        // active track index.
+        LiveData<Integer> currentlyActiveTrackData = mMusicRepository.getCurrentlyActiveTrackData();
+        mCurrActiveTrackIndex = currentlyActiveTrackData.getValue();
+        currentlyActiveTrackData.observe(this, new Observer<Integer>() {
+            @Override
+            public void onChanged(@Nullable Integer integer) {
+                mCurrActiveTrackIndex = integer;
+                if (mCurrActiveTrackIndex < 0) {
+                    return;
+                }
+
+                // Create the media player if necessary, set its data to the currently active track
+                // and call prepare(). This will eventually result in an asynchronous call to
+                // our onPrepared() method which will transition from PREPARING into PLAYING state.
+                createMediaPlayerIfNeeded();
+                try {
+                    mMusicRepository.setState(MusicRepository.STATE_PREPARING);
+                    @RawRes int trackRawRes = mTracks.get(mCurrActiveTrackIndex).getTrackRes();
+                    mMediaPlayer.setDataSource(getBaseContext(),
+                            Uri.parse(RESOURCE_PREFIX + trackRawRes));
+                    mMediaPlayer.prepare();
+                } catch (IOException ioe) {
+                }
+                // As the media player is preparing the track, update the media session and the
+                // notification with the metadata of that track.
+                updateAudioMetadata();
+                updateNotification();
+            }
+        });
+
+        // Register self as the observer on the LiveData object that wraps the playback state.
+        LiveData<Integer> stateData = mMusicRepository.getStateData();
+        mCurrPlaybackState = stateData.getValue();
+        stateData.observe(this, new Observer<Integer>() {
+            @Override
+            public void onChanged(@Nullable Integer integer) {
+                mCurrPlaybackState = integer;
+                switch (mCurrPlaybackState) {
+                    case MusicRepository.STATE_INITIAL:
+                        createMediaPlayerIfNeeded();
+                        break;
+                    case MusicRepository.STATE_PLAYING:
+                        // Start the media player and update the ongoing notification
+                        configAndStartMediaPlayer();
+                        updateNotification();
+                        break;
+                    case MusicRepository.STATE_PAUSED:
+                        // Pause the media player and update the ongoing notification
+                        mMediaPlayer.pause();
+                        updateNotification();
+                }
+            }
+        });
+    }
+
+    private void createMediaPlayerIfNeeded() {
+        if (mMediaPlayer == null) {
+            mMediaPlayer = new MediaPlayer();
+            // Make sure the media player will acquire a wake-lock while playing. If we don't do
+            // that, the CPU might go to sleep while the song is playing, causing playback to stop.
+            //
+            // Remember that to use this, we have to declare the android.permission.WAKE_LOCK
+            // permission in AndroidManifest.xml.
+            mMediaPlayer.setWakeMode(getApplicationContext(), PowerManager.PARTIAL_WAKE_LOCK);
+            // we want the media player to notify us when it's ready preparing, and when it's done
+            // playing:
+            mMediaPlayer.setOnPreparedListener(this);
+            mMediaPlayer.setOnCompletionListener(this);
+        } else {
+            mMediaPlayer.reset();
+        }
+    }
+
+    @Override
+    public int onStartCommand(Intent intent, int flags, int startId) {
+        super.onStartCommand(intent, flags, startId);
+
+        // Note that we don't do anything for the START action. The purpose of that action
+        // is to start the service. As the service registers itself to observe changes to
+        // playback state and current track, it will start the matching flows as a response
+        // to those changes.
+        // Here we handle service-internal actions that are registered on notification intents.
+        if (intent.getAction().equals(ACTION_PLAY)) {
+            processPlayRequest();
+        } else if (intent.getAction().equals(ACTION_PAUSE)) {
+            processPauseRequest();
+        } else if (intent.getAction().equals(ACTION_STOP)) {
+            processStopRequest();
+        } else if (intent.getAction().equals(ACTION_NEXT)) {
+            processNextRequest();
+        } else if (intent.getAction().equals(ACTION_PREV)) {
+            processPreviousRequest();
+        }
+
+        return START_NOT_STICKY;
+    }
+
+    private void processPlayRequest() {
+        // The logic here is different depending on our current state
+        if (mCurrPlaybackState == MusicRepository.STATE_STOPPED) {
+            // If we're stopped, just go ahead to the next song and start playing.
+            playNextSong();
+        } else if (mCurrPlaybackState == MusicRepository.STATE_PAUSED) {
+            // If we're paused, just continue playback. We are registered to listen to the changes
+            // in LiveData that tracks the playback state, and that observer will update our ongoing
+            // notification and resume the playback.
+            mMusicRepository.setState(MusicRepository.STATE_PLAYING);
+        }
+    }
+
+    private void processPauseRequest() {
+        if (mCurrPlaybackState == MusicRepository.STATE_PLAYING) {
+            // Move to the paused state. We are registered
+            // to listen to the changes in LiveData that tracks the playback state,
+            // and that observer will update our ongoing notification and pause the media
+            // player.
+            mMusicRepository.setState(MusicRepository.STATE_PAUSED);
+        }
+    }
+
+    private void processStopRequest() {
+        processStopRequest(false);
+    }
+
+    private void processStopRequest(boolean force) {
+        if (mCurrPlaybackState != MusicRepository.STATE_STOPPED || force) {
+            mMusicRepository.setState(MusicRepository.STATE_STOPPED);
+            // let go of all resources...
+            relaxResources(true);
+            // cancel the notification
+            mNotificationManager.cancel(NOTIFICATION_ID);
+            // service is no longer necessary. Will be started again if needed.
+            stopSelf();
+        }
+    }
+
+    private void processNextRequest() {
+        if (mCurrPlaybackState != MusicRepository.STATE_STOPPED) {
+            playNextSong();
+        }
+    }
+
+    private void processPreviousRequest() {
+        if (mCurrPlaybackState != MusicRepository.STATE_STOPPED) {
+            playPrevSong();
+        }
+    }
+
+    /**
+     * Releases resources used by the service for playback. This includes the "foreground service"
+     * status and notification, the wake locks and possibly the MediaPlayer.
+     *
+     * @param releaseMediaPlayer Indicates whether the Media Player should also be released or not
+     */
+    private void relaxResources(boolean releaseMediaPlayer) {
+        // stop being a foreground service
+        //stopForeground(true);
+        // stop and release the Media Player, if it's available
+        if (releaseMediaPlayer && mMediaPlayer != null) {
+            mMediaPlayer.reset();
+            mMediaPlayer.release();
+            mMediaPlayer = null;
+        }
+    }
+
+    /**
+     * Reconfigures MediaPlayer according to audio focus settings and starts/restarts it. This
+     * method starts/restarts the MediaPlayer respecting the current audio focus state. So if
+     * we have focus, it will play normally; if we don't have focus, it will either leave the
+     * MediaPlayer paused or set it to a low volume, depending on what is allowed by the
+     * current focus settings. This method assumes mPlayer != null, so if you are calling it,
+     * you have to do so from a context where you are sure this is the case.
+     */
+    private void configAndStartMediaPlayer() {
+        mMediaPlayer.setVolume(1.0f, 1.0f); // we can be loud
+        if (!mMediaPlayer.isPlaying()) {
+            mMediaPlayer.start();
+        }
+    }
+
+    /**
+     * Starts playing the next song in our repository.
+     */
+    private void playNextSong() {
+        relaxResources(false); // release everything except MediaPlayer
+
+        // Ask the repository to go to the next track. We are registered to listen to the
+        // changes in LiveData that tracks the current track, and that observer will point the
+        // media player to the right URI
+        mMusicRepository.goToNextTrack();
+    }
+
+    /**
+     * Starts playing the previous song in our repository.
+     */
+    private void playPrevSong() {
+        relaxResources(false); // release everything except MediaPlayer
+
+        // Ask the repository to go to the next track. We are registered to listen to the
+        // changes in LiveData that tracks the current track, and that observer will point the
+        // media player to the right URI
+        mMusicRepository.goToPreviousTrack();
+    }
+
+    /**
+     * Called when media player is done playing current song.
+     */
+    public void onCompletion(MediaPlayer player) {
+        // The media player finished playing the current song, so we go ahead and start the next.
+        playNextSong();
+    }
+
+    /** Called when media player is done preparing. */
+    public void onPrepared(MediaPlayer player) {
+        // The media player is done preparing. That means we can start playing!
+        // We are registered to listen to the changes in LiveData that tracks the playback state,
+        // and that observer will update our ongoing notification
+        mMusicRepository.setState(MusicRepository.STATE_PLAYING);
+    }
+
+    /**
+     * Configures service as a foreground service. A foreground service is a service that's doing
+     * something the user is actively aware of (such as playing music), and must appear to the
+     * user as a notification. That's why we create the notification here.
+     */
+    private void populateNotificationBuilderContent(String text) {
+        PendingIntent pi = PendingIntent.getActivity(getApplicationContext(),
+                (int) (System.currentTimeMillis() & 0xfffffff),
+                new Intent().setClass(getApplicationContext(), MainActivity.class)
+                        .setFlags(Intent.FLAG_ACTIVITY_SINGLE_TOP),
+                PendingIntent.FLAG_UPDATE_CURRENT);
+
+        boolean isPlaying = (mCurrPlaybackState == MusicRepository.STATE_PLAYING);
+
+        // Build the notification object.
+        mNotificationBuilder = new NotificationCompat.Builder(getApplicationContext());
+        mNotificationBuilder.setSmallIcon(R.drawable.ic_play_arrow_white_24dp);
+        mNotificationBuilder.setTicker(text);
+        mNotificationBuilder.setWhen(System.currentTimeMillis());
+        mNotificationBuilder.setContentTitle("RandomMusicPlayer");
+        mNotificationBuilder.setContentText(text);
+        mNotificationBuilder.setContentIntent(pi);
+        mNotificationBuilder.setOngoing(isPlaying);
+
+        int primaryActionDrawable = isPlaying ? android.R.drawable.ic_media_pause
+                : android.R.drawable.ic_media_play;
+        PendingIntent primaryActionIntent = isPlaying
+                ? PendingIntent.getService(this, 12,
+                        new Intent(this, MusicService.class).setAction(ACTION_PAUSE), 0)
+                : PendingIntent.getService(this, 13,
+                        new Intent(this, MusicService.class).setAction(ACTION_PLAY), 0);
+        String primaryActionName = isPlaying ? "pause" : "play";
+
+        mNotificationBuilder.addAction(android.R.drawable.ic_media_previous, "previous",
+                PendingIntent.getService(this, 10,
+                        new Intent(this, MusicService.class).setAction(ACTION_PREV), 0));
+        mNotificationBuilder.addAction(primaryActionDrawable, primaryActionName,
+                primaryActionIntent);
+        mNotificationBuilder.addAction(android.R.drawable.ic_media_next, "next",
+                PendingIntent.getService(this, 11,
+                        new Intent(this, MusicService.class).setAction(ACTION_NEXT), 0));
+
+        mNotificationBuilder.setStyle(new NotificationCompat.MediaStyle()
+                .setShowActionsInCompactView(0, 1, 2)
+                .setMediaSession(mMediaSession.getSessionToken()));
+    }
+
+    private void updateNotification() {
+        if (mCurrPlaybackState == MusicRepository.STATE_INITIAL) {
+            return;
+        }
+
+        if (mNotificationBuilder == null) {
+            // This is the very first time we're creating our ongoing notification, and marking
+            // the service to be in the foreground.
+            populateNotificationBuilderContent("Initializing...");
+            startForeground(NOTIFICATION_ID, mNotificationBuilder.build());
+            return;
+        }
+
+        TrackMetadata currTrack = mTracks.get(mCurrActiveTrackIndex);
+        populateNotificationBuilderContent(currTrack.getTitle()
+                + " by " + currTrack.getArtist());
+        mNotificationManager.notify(NOTIFICATION_ID, mNotificationBuilder.build());
+    }
+
+    private void updateAudioMetadata() {
+        if (mCurrPlaybackState == MusicRepository.STATE_INITIAL) {
+            return;
+        }
+        Bitmap albumArt = BitmapFactory.decodeResource(getResources(), R.drawable.nougat_bg_2x);
+        // Update the current metadata
+        TrackMetadata current = mTracks.get(mCurrActiveTrackIndex);
+        mMediaSession.setMetadata(new MediaMetadataCompat.Builder()
+                .putBitmap(MediaMetadataCompat.METADATA_KEY_ALBUM_ART, albumArt)
+                .putString(MediaMetadataCompat.METADATA_KEY_ARTIST, current.getArtist())
+                .putString(MediaMetadataCompat.METADATA_KEY_TITLE, "Track #" + current.getTitle())
+                .build());
+    }
+
+    @Nullable
+    @Override
+    public IBinder onBind(Intent intent) {
+        super.onBind(intent);
+        return null;
+    }
+
+    @Override
+    public void onDestroy() {
+        super.onDestroy();
+        if (mMediaPlayer != null) {
+            mMediaPlayer.release();
+            mNotificationManager.cancel(NOTIFICATION_ID);
+            stopForeground(true);
+        }
+    }
+}
diff --git a/samples-flatfoot/MusicPlayer/app/src/main/java/com/android/sample/musicplayer/adapter/MusicTrackListAdapter.java b/samples-flatfoot/MusicPlayer/app/src/main/java/com/android/sample/musicplayer/adapter/MusicTrackListAdapter.java
new file mode 100644
index 0000000..dd03302
--- /dev/null
+++ b/samples-flatfoot/MusicPlayer/app/src/main/java/com/android/sample/musicplayer/adapter/MusicTrackListAdapter.java
@@ -0,0 +1,96 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.sample.musicplayer.adapter;
+
+import android.databinding.DataBindingUtil;
+import android.support.v7.widget.RecyclerView;
+import android.support.v7.widget.RecyclerView.Adapter;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+
+import com.android.sample.musicplayer.MusicRepository;
+import com.android.sample.musicplayer.MusicRepository.TrackMetadata;
+import com.android.sample.musicplayer.R;
+import com.android.sample.musicplayer.databinding.MainRowBinding;
+
+import java.util.List;
+
+/**
+ * Adapter for the list of music tracks.
+ */
+public class MusicTrackListAdapter extends Adapter<MusicTrackListAdapter.TrackBindingHolder> {
+    private List<TrackMetadata> mTracks;
+    private int mActiveTrackIndex;
+
+    /**
+     * Holder for the track row.
+     */
+    public static class TrackBindingHolder extends RecyclerView.ViewHolder {
+        private MainRowBinding mViewDataBinding;
+
+        public TrackBindingHolder(MainRowBinding viewDataBinding) {
+            super(viewDataBinding.getRoot());
+            mViewDataBinding = viewDataBinding;
+        }
+
+        public MainRowBinding getBinding() {
+            return mViewDataBinding;
+        }
+    }
+
+    public MusicTrackListAdapter(List<TrackMetadata> tracks) {
+        mTracks = tracks;
+    }
+
+    @Override
+    public TrackBindingHolder onCreateViewHolder(ViewGroup parent, int viewType) {
+        MainRowBinding binding = DataBindingUtil.inflate(LayoutInflater.from(parent.getContext()),
+                R.layout.main_row, parent, false);
+        return new TrackBindingHolder(binding);
+    }
+
+    @Override
+    public void onBindViewHolder(TrackBindingHolder holder, final int position) {
+        MainRowBinding binding = holder.getBinding();
+        binding.setTrack(mTracks.get(position));
+        binding.setHandler(new View.OnClickListener() {
+            @Override
+            public void onClick(View v) {
+                // Update the LiveData-wrapped current track index directly on the repository.
+                // Our service observes those changes and will start the flow of preparing and
+                // playing back this track.
+                MusicRepository.getInstance().setTrack(position);
+            }
+        });
+        binding.getRoot().setSelected(position == mActiveTrackIndex);
+        binding.executePendingBindings();
+    }
+
+    @Override
+    public int getItemCount() {
+        return mTracks.size();
+    }
+
+    public void setActiveTrackIndex(int activeTrackIndex) {
+        if (mActiveTrackIndex != activeTrackIndex) {
+            int previousActiveTrackIndex = mActiveTrackIndex;
+            mActiveTrackIndex = activeTrackIndex;
+            notifyItemChanged(previousActiveTrackIndex);
+            notifyItemChanged(mActiveTrackIndex);
+        }
+    }
+}
diff --git a/samples-flatfoot/MusicPlayer/app/src/main/res/drawable-hdpi/ic_pause_white_24dp.png b/samples-flatfoot/MusicPlayer/app/src/main/res/drawable-hdpi/ic_pause_white_24dp.png
new file mode 100644
index 0000000..4d2ea05
--- /dev/null
+++ b/samples-flatfoot/MusicPlayer/app/src/main/res/drawable-hdpi/ic_pause_white_24dp.png
Binary files differ
diff --git a/samples-flatfoot/MusicPlayer/app/src/main/res/drawable-hdpi/ic_pause_white_36dp.png b/samples-flatfoot/MusicPlayer/app/src/main/res/drawable-hdpi/ic_pause_white_36dp.png
new file mode 100644
index 0000000..1d02439
--- /dev/null
+++ b/samples-flatfoot/MusicPlayer/app/src/main/res/drawable-hdpi/ic_pause_white_36dp.png
Binary files differ
diff --git a/samples-flatfoot/MusicPlayer/app/src/main/res/drawable-hdpi/ic_play_arrow_white_24dp.png b/samples-flatfoot/MusicPlayer/app/src/main/res/drawable-hdpi/ic_play_arrow_white_24dp.png
new file mode 100644
index 0000000..57c9fa5
--- /dev/null
+++ b/samples-flatfoot/MusicPlayer/app/src/main/res/drawable-hdpi/ic_play_arrow_white_24dp.png
Binary files differ
diff --git a/samples-flatfoot/MusicPlayer/app/src/main/res/drawable-hdpi/ic_play_arrow_white_36dp.png b/samples-flatfoot/MusicPlayer/app/src/main/res/drawable-hdpi/ic_play_arrow_white_36dp.png
new file mode 100644
index 0000000..29adeed
--- /dev/null
+++ b/samples-flatfoot/MusicPlayer/app/src/main/res/drawable-hdpi/ic_play_arrow_white_36dp.png
Binary files differ
diff --git a/samples-flatfoot/MusicPlayer/app/src/main/res/drawable-mdpi/ic_pause_white_24dp.png b/samples-flatfoot/MusicPlayer/app/src/main/res/drawable-mdpi/ic_pause_white_24dp.png
new file mode 100644
index 0000000..2272d47
--- /dev/null
+++ b/samples-flatfoot/MusicPlayer/app/src/main/res/drawable-mdpi/ic_pause_white_24dp.png
Binary files differ
diff --git a/samples-flatfoot/MusicPlayer/app/src/main/res/drawable-mdpi/ic_pause_white_36dp.png b/samples-flatfoot/MusicPlayer/app/src/main/res/drawable-mdpi/ic_pause_white_36dp.png
new file mode 100644
index 0000000..4d2ea05
--- /dev/null
+++ b/samples-flatfoot/MusicPlayer/app/src/main/res/drawable-mdpi/ic_pause_white_36dp.png
Binary files differ
diff --git a/samples-flatfoot/MusicPlayer/app/src/main/res/drawable-mdpi/ic_play_arrow_white_24dp.png b/samples-flatfoot/MusicPlayer/app/src/main/res/drawable-mdpi/ic_play_arrow_white_24dp.png
new file mode 100644
index 0000000..c61e948
--- /dev/null
+++ b/samples-flatfoot/MusicPlayer/app/src/main/res/drawable-mdpi/ic_play_arrow_white_24dp.png
Binary files differ
diff --git a/samples-flatfoot/MusicPlayer/app/src/main/res/drawable-mdpi/ic_play_arrow_white_36dp.png b/samples-flatfoot/MusicPlayer/app/src/main/res/drawable-mdpi/ic_play_arrow_white_36dp.png
new file mode 100644
index 0000000..57c9fa5
--- /dev/null
+++ b/samples-flatfoot/MusicPlayer/app/src/main/res/drawable-mdpi/ic_play_arrow_white_36dp.png
Binary files differ
diff --git a/samples-flatfoot/MusicPlayer/app/src/main/res/drawable-xhdpi/ic_pause_white_24dp.png b/samples-flatfoot/MusicPlayer/app/src/main/res/drawable-xhdpi/ic_pause_white_24dp.png
new file mode 100644
index 0000000..f49aed7
--- /dev/null
+++ b/samples-flatfoot/MusicPlayer/app/src/main/res/drawable-xhdpi/ic_pause_white_24dp.png
Binary files differ
diff --git a/samples-flatfoot/MusicPlayer/app/src/main/res/drawable-xhdpi/ic_pause_white_36dp.png b/samples-flatfoot/MusicPlayer/app/src/main/res/drawable-xhdpi/ic_pause_white_36dp.png
new file mode 100644
index 0000000..7192ad4
--- /dev/null
+++ b/samples-flatfoot/MusicPlayer/app/src/main/res/drawable-xhdpi/ic_pause_white_36dp.png
Binary files differ
diff --git a/samples-flatfoot/MusicPlayer/app/src/main/res/drawable-xhdpi/ic_play_arrow_white_24dp.png b/samples-flatfoot/MusicPlayer/app/src/main/res/drawable-xhdpi/ic_play_arrow_white_24dp.png
new file mode 100644
index 0000000..a3c80e7
--- /dev/null
+++ b/samples-flatfoot/MusicPlayer/app/src/main/res/drawable-xhdpi/ic_play_arrow_white_24dp.png
Binary files differ
diff --git a/samples-flatfoot/MusicPlayer/app/src/main/res/drawable-xhdpi/ic_play_arrow_white_36dp.png b/samples-flatfoot/MusicPlayer/app/src/main/res/drawable-xhdpi/ic_play_arrow_white_36dp.png
new file mode 100644
index 0000000..547ef30
--- /dev/null
+++ b/samples-flatfoot/MusicPlayer/app/src/main/res/drawable-xhdpi/ic_play_arrow_white_36dp.png
Binary files differ
diff --git a/samples-flatfoot/MusicPlayer/app/src/main/res/drawable-xxhdpi/ic_pause_white_24dp.png b/samples-flatfoot/MusicPlayer/app/src/main/res/drawable-xxhdpi/ic_pause_white_24dp.png
new file mode 100644
index 0000000..7192ad4
--- /dev/null
+++ b/samples-flatfoot/MusicPlayer/app/src/main/res/drawable-xxhdpi/ic_pause_white_24dp.png
Binary files differ
diff --git a/samples-flatfoot/MusicPlayer/app/src/main/res/drawable-xxhdpi/ic_pause_white_36dp.png b/samples-flatfoot/MusicPlayer/app/src/main/res/drawable-xxhdpi/ic_pause_white_36dp.png
new file mode 100644
index 0000000..a03bad2
--- /dev/null
+++ b/samples-flatfoot/MusicPlayer/app/src/main/res/drawable-xxhdpi/ic_pause_white_36dp.png
Binary files differ
diff --git a/samples-flatfoot/MusicPlayer/app/src/main/res/drawable-xxhdpi/ic_play_arrow_white_24dp.png b/samples-flatfoot/MusicPlayer/app/src/main/res/drawable-xxhdpi/ic_play_arrow_white_24dp.png
new file mode 100644
index 0000000..547ef30
--- /dev/null
+++ b/samples-flatfoot/MusicPlayer/app/src/main/res/drawable-xxhdpi/ic_play_arrow_white_24dp.png
Binary files differ
diff --git a/samples-flatfoot/MusicPlayer/app/src/main/res/drawable-xxhdpi/ic_play_arrow_white_36dp.png b/samples-flatfoot/MusicPlayer/app/src/main/res/drawable-xxhdpi/ic_play_arrow_white_36dp.png
new file mode 100644
index 0000000..23bb1ba
--- /dev/null
+++ b/samples-flatfoot/MusicPlayer/app/src/main/res/drawable-xxhdpi/ic_play_arrow_white_36dp.png
Binary files differ
diff --git a/samples-flatfoot/MusicPlayer/app/src/main/res/drawable-xxxhdpi/ic_pause_white_24dp.png b/samples-flatfoot/MusicPlayer/app/src/main/res/drawable-xxxhdpi/ic_pause_white_24dp.png
new file mode 100644
index 0000000..660ac65
--- /dev/null
+++ b/samples-flatfoot/MusicPlayer/app/src/main/res/drawable-xxxhdpi/ic_pause_white_24dp.png
Binary files differ
diff --git a/samples-flatfoot/MusicPlayer/app/src/main/res/drawable-xxxhdpi/ic_pause_white_36dp.png b/samples-flatfoot/MusicPlayer/app/src/main/res/drawable-xxxhdpi/ic_pause_white_36dp.png
new file mode 100644
index 0000000..3ea7e03
--- /dev/null
+++ b/samples-flatfoot/MusicPlayer/app/src/main/res/drawable-xxxhdpi/ic_pause_white_36dp.png
Binary files differ
diff --git a/samples-flatfoot/MusicPlayer/app/src/main/res/drawable-xxxhdpi/ic_play_arrow_white_24dp.png b/samples-flatfoot/MusicPlayer/app/src/main/res/drawable-xxxhdpi/ic_play_arrow_white_24dp.png
new file mode 100644
index 0000000..be5c062
--- /dev/null
+++ b/samples-flatfoot/MusicPlayer/app/src/main/res/drawable-xxxhdpi/ic_play_arrow_white_24dp.png
Binary files differ
diff --git a/samples-flatfoot/MusicPlayer/app/src/main/res/drawable-xxxhdpi/ic_play_arrow_white_36dp.png b/samples-flatfoot/MusicPlayer/app/src/main/res/drawable-xxxhdpi/ic_play_arrow_white_36dp.png
new file mode 100644
index 0000000..2745c3a
--- /dev/null
+++ b/samples-flatfoot/MusicPlayer/app/src/main/res/drawable-xxxhdpi/ic_play_arrow_white_36dp.png
Binary files differ
diff --git a/samples-flatfoot/MusicPlayer/app/src/main/res/drawable/nougat_bg_2x.jpg b/samples-flatfoot/MusicPlayer/app/src/main/res/drawable/nougat_bg_2x.jpg
new file mode 100644
index 0000000..5c7295e
--- /dev/null
+++ b/samples-flatfoot/MusicPlayer/app/src/main/res/drawable/nougat_bg_2x.jpg
Binary files differ
diff --git a/samples-flatfoot/MusicPlayer/app/src/main/res/drawable/row_selector.xml b/samples-flatfoot/MusicPlayer/app/src/main/res/drawable/row_selector.xml
new file mode 100644
index 0000000..f2736f4a
--- /dev/null
+++ b/samples-flatfoot/MusicPlayer/app/src/main/res/drawable/row_selector.xml
@@ -0,0 +1,6 @@
+<?xml version="1.0" encoding="utf-8"?>
+
+<selector xmlns:android="http://schemas.android.com/apk/res/android">
+    <item android:drawable="@color/currentTrack" android:state_selected="true"/>
+    <item android:drawable="@android:color/transparent" android:state_selected="false"/>
+</selector>
\ No newline at end of file
diff --git a/samples-flatfoot/MusicPlayer/app/src/main/res/layout/activity_main.xml b/samples-flatfoot/MusicPlayer/app/src/main/res/layout/activity_main.xml
new file mode 100644
index 0000000..961b1da
--- /dev/null
+++ b/samples-flatfoot/MusicPlayer/app/src/main/res/layout/activity_main.xml
@@ -0,0 +1,35 @@
+<?xml version="1.0" encoding="utf-8"?>
+<android.support.design.widget.CoordinatorLayout
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:app="http://schemas.android.com/apk/res-auto"
+    xmlns:tools="http://schemas.android.com/tools"
+    android:layout_width="match_parent"
+    android:layout_height="match_parent"
+    android:fitsSystemWindows="true"
+    tools:context="com.android.sample.musicplayer.MainActivity">
+
+    <android.support.design.widget.AppBarLayout
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        android:theme="@style/AppTheme.AppBarOverlay">
+
+        <android.support.v7.widget.Toolbar
+            android:id="@+id/toolbar"
+            android:layout_width="match_parent"
+            android:layout_height="?attr/actionBarSize"
+            android:background="?attr/colorPrimary"
+            app:popupTheme="@style/AppTheme.PopupOverlay"/>
+
+    </android.support.design.widget.AppBarLayout>
+
+    <include layout="@layout/content_main"/>
+
+    <android.support.design.widget.FloatingActionButton
+        android:id="@+id/fab"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:layout_gravity="bottom|end"
+        android:layout_margin="@dimen/fab_margin"
+        app:srcCompat="@drawable/ic_play_arrow_white_36dp"/>
+
+</android.support.design.widget.CoordinatorLayout>
diff --git a/samples-flatfoot/MusicPlayer/app/src/main/res/layout/content_main.xml b/samples-flatfoot/MusicPlayer/app/src/main/res/layout/content_main.xml
new file mode 100644
index 0000000..7da0c97
--- /dev/null
+++ b/samples-flatfoot/MusicPlayer/app/src/main/res/layout/content_main.xml
@@ -0,0 +1,11 @@
+<?xml version="1.0" encoding="utf-8"?>
+<android.support.v7.widget.RecyclerView
+    android:id="@+id/recycler"
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:app="http://schemas.android.com/apk/res-auto"
+    xmlns:tools="http://schemas.android.com/tools"
+    android:layout_width="match_parent"
+    android:layout_height="match_parent"
+    app:layout_behavior="@string/appbar_scrolling_view_behavior"
+    tools:context="com.android.sample.musicplayer.MainActivity"
+    tools:showIn="@layout/activity_main" />
diff --git a/samples-flatfoot/MusicPlayer/app/src/main/res/layout/main_row.xml b/samples-flatfoot/MusicPlayer/app/src/main/res/layout/main_row.xml
new file mode 100644
index 0000000..7d38dd6
--- /dev/null
+++ b/samples-flatfoot/MusicPlayer/app/src/main/res/layout/main_row.xml
@@ -0,0 +1,45 @@
+<?xml version="1.0" encoding="utf-8"?>
+<layout xmlns:android="http://schemas.android.com/apk/res/android">
+    <data>
+        <import type="java.lang.Integer"/>
+        <variable name="track" type="com.android.sample.musicplayer.MusicRepository.TrackMetadata"/>
+        <variable name="handler" type="android.view.View.OnClickListener"/>
+    </data>
+
+    <RelativeLayout
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        android:background="@drawable/row_selector"
+        android:clickable="true"
+        android:focusable="true"
+        android:onClick="@{handler}"
+        android:padding="8dp">
+        <TextView
+            android:id="@+id/index"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:layout_centerVertical="true"
+            android:paddingLeft="8dp"
+            android:paddingRight="16dp"
+            android:text="@{Integer.toString(track.index)}"
+            android:textAppearance="@style/TextAppearance.AppCompat.Large"/>
+        <TextView
+            android:id="@+id/title"
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:layout_toRightOf="@id/index"
+            android:text="@{track.title}"
+            android:textAppearance="@style/TextAppearance.AppCompat.Small"
+            android:textColor="#222"/>
+        <TextView
+            android:id="@+id/artist"
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:layout_below="@id/title"
+            android:layout_toRightOf="@id/index"
+            android:text="@{track.artist}"
+            android:textAppearance="@style/TextAppearance.AppCompat.Small"
+            android:textColor="#666"/>
+    </RelativeLayout>
+</layout>
+
diff --git a/samples-flatfoot/MusicPlayer/app/src/main/res/mipmap-hdpi/ic_launcher.png b/samples-flatfoot/MusicPlayer/app/src/main/res/mipmap-hdpi/ic_launcher.png
new file mode 100644
index 0000000..cde69bc
--- /dev/null
+++ b/samples-flatfoot/MusicPlayer/app/src/main/res/mipmap-hdpi/ic_launcher.png
Binary files differ
diff --git a/samples-flatfoot/MusicPlayer/app/src/main/res/mipmap-mdpi/ic_launcher.png b/samples-flatfoot/MusicPlayer/app/src/main/res/mipmap-mdpi/ic_launcher.png
new file mode 100644
index 0000000..c133a0c
--- /dev/null
+++ b/samples-flatfoot/MusicPlayer/app/src/main/res/mipmap-mdpi/ic_launcher.png
Binary files differ
diff --git a/samples-flatfoot/MusicPlayer/app/src/main/res/mipmap-xhdpi/ic_launcher.png b/samples-flatfoot/MusicPlayer/app/src/main/res/mipmap-xhdpi/ic_launcher.png
new file mode 100644
index 0000000..bfa42f0
--- /dev/null
+++ b/samples-flatfoot/MusicPlayer/app/src/main/res/mipmap-xhdpi/ic_launcher.png
Binary files differ
diff --git a/samples-flatfoot/MusicPlayer/app/src/main/res/mipmap-xxhdpi/ic_launcher.png b/samples-flatfoot/MusicPlayer/app/src/main/res/mipmap-xxhdpi/ic_launcher.png
new file mode 100644
index 0000000..324e72c
--- /dev/null
+++ b/samples-flatfoot/MusicPlayer/app/src/main/res/mipmap-xxhdpi/ic_launcher.png
Binary files differ
diff --git a/samples-flatfoot/MusicPlayer/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png b/samples-flatfoot/MusicPlayer/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png
new file mode 100644
index 0000000..aee44e1
--- /dev/null
+++ b/samples-flatfoot/MusicPlayer/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png
Binary files differ
diff --git a/samples-flatfoot/MusicPlayer/app/src/main/res/raw/track1.mp3 b/samples-flatfoot/MusicPlayer/app/src/main/res/raw/track1.mp3
new file mode 100644
index 0000000..ec6668b
--- /dev/null
+++ b/samples-flatfoot/MusicPlayer/app/src/main/res/raw/track1.mp3
Binary files differ
diff --git a/samples-flatfoot/MusicPlayer/app/src/main/res/raw/track2.mp3 b/samples-flatfoot/MusicPlayer/app/src/main/res/raw/track2.mp3
new file mode 100644
index 0000000..b91854b
--- /dev/null
+++ b/samples-flatfoot/MusicPlayer/app/src/main/res/raw/track2.mp3
Binary files differ
diff --git a/samples-flatfoot/MusicPlayer/app/src/main/res/raw/track3.mp3 b/samples-flatfoot/MusicPlayer/app/src/main/res/raw/track3.mp3
new file mode 100644
index 0000000..4d765e1
--- /dev/null
+++ b/samples-flatfoot/MusicPlayer/app/src/main/res/raw/track3.mp3
Binary files differ
diff --git a/samples-flatfoot/MusicPlayer/app/src/main/res/raw/track4.mp3 b/samples-flatfoot/MusicPlayer/app/src/main/res/raw/track4.mp3
new file mode 100644
index 0000000..e4e0320
--- /dev/null
+++ b/samples-flatfoot/MusicPlayer/app/src/main/res/raw/track4.mp3
Binary files differ
diff --git a/samples-flatfoot/MusicPlayer/app/src/main/res/raw/track5.mp3 b/samples-flatfoot/MusicPlayer/app/src/main/res/raw/track5.mp3
new file mode 100644
index 0000000..412e21a
--- /dev/null
+++ b/samples-flatfoot/MusicPlayer/app/src/main/res/raw/track5.mp3
Binary files differ
diff --git a/samples-flatfoot/MusicPlayer/app/src/main/res/raw/track6.mp3 b/samples-flatfoot/MusicPlayer/app/src/main/res/raw/track6.mp3
new file mode 100644
index 0000000..b82bb02
--- /dev/null
+++ b/samples-flatfoot/MusicPlayer/app/src/main/res/raw/track6.mp3
Binary files differ
diff --git a/samples-flatfoot/MusicPlayer/app/src/main/res/raw/track7.mp3 b/samples-flatfoot/MusicPlayer/app/src/main/res/raw/track7.mp3
new file mode 100644
index 0000000..cb42ec0
--- /dev/null
+++ b/samples-flatfoot/MusicPlayer/app/src/main/res/raw/track7.mp3
Binary files differ
diff --git a/samples-flatfoot/MusicPlayer/app/src/main/res/raw/track8.mp3 b/samples-flatfoot/MusicPlayer/app/src/main/res/raw/track8.mp3
new file mode 100644
index 0000000..37dd232
--- /dev/null
+++ b/samples-flatfoot/MusicPlayer/app/src/main/res/raw/track8.mp3
Binary files differ
diff --git a/samples-flatfoot/MusicPlayer/app/src/main/res/raw/track9.mp3 b/samples-flatfoot/MusicPlayer/app/src/main/res/raw/track9.mp3
new file mode 100644
index 0000000..3c99c3a
--- /dev/null
+++ b/samples-flatfoot/MusicPlayer/app/src/main/res/raw/track9.mp3
Binary files differ
diff --git a/samples-flatfoot/MusicPlayer/app/src/main/res/values-v21/styles.xml b/samples-flatfoot/MusicPlayer/app/src/main/res/values-v21/styles.xml
new file mode 100644
index 0000000..6b23c86
--- /dev/null
+++ b/samples-flatfoot/MusicPlayer/app/src/main/res/values-v21/styles.xml
@@ -0,0 +1,8 @@
+<resources>
+    <style name="AppTheme.NoActionBar">
+        <item name="windowActionBar">false</item>
+        <item name="windowNoTitle">true</item>
+        <item name="android:windowDrawsSystemBarBackgrounds">true</item>
+        <item name="android:statusBarColor">@android:color/transparent</item>
+    </style>
+</resources>
diff --git a/samples-flatfoot/MusicPlayer/app/src/main/res/values-w820dp/dimens.xml b/samples-flatfoot/MusicPlayer/app/src/main/res/values-w820dp/dimens.xml
new file mode 100644
index 0000000..63fc816
--- /dev/null
+++ b/samples-flatfoot/MusicPlayer/app/src/main/res/values-w820dp/dimens.xml
@@ -0,0 +1,6 @@
+<resources>
+    <!-- Example customization of dimensions originally defined in res/values/dimens.xml
+         (such as screen margins) for screens with more than 820dp of available width. This
+         would include 7" and 10" devices in landscape (~960dp and ~1280dp respectively). -->
+    <dimen name="activity_horizontal_margin">64dp</dimen>
+</resources>
diff --git a/samples-flatfoot/MusicPlayer/app/src/main/res/values/colors.xml b/samples-flatfoot/MusicPlayer/app/src/main/res/values/colors.xml
new file mode 100644
index 0000000..de13610
--- /dev/null
+++ b/samples-flatfoot/MusicPlayer/app/src/main/res/values/colors.xml
@@ -0,0 +1,8 @@
+<?xml version="1.0" encoding="utf-8"?>
+<resources>
+    <color name="colorPrimary">#3F51B5</color>
+    <color name="colorPrimaryDark">#303F9F</color>
+    <color name="colorAccent">#FFAB00</color>
+
+    <color name="currentTrack">#64FFDA</color>
+</resources>
diff --git a/samples-flatfoot/MusicPlayer/app/src/main/res/values/dimens.xml b/samples-flatfoot/MusicPlayer/app/src/main/res/values/dimens.xml
new file mode 100644
index 0000000..812cb7b
--- /dev/null
+++ b/samples-flatfoot/MusicPlayer/app/src/main/res/values/dimens.xml
@@ -0,0 +1,6 @@
+<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="fab_margin">16dp</dimen>
+</resources>
diff --git a/samples-flatfoot/MusicPlayer/app/src/main/res/values/strings.xml b/samples-flatfoot/MusicPlayer/app/src/main/res/values/strings.xml
new file mode 100644
index 0000000..4ca3fbf
--- /dev/null
+++ b/samples-flatfoot/MusicPlayer/app/src/main/res/values/strings.xml
@@ -0,0 +1,4 @@
+<resources>
+    <string name="app_name">MusicPlayer</string>
+    <string name="action_settings">Settings</string>
+</resources>
diff --git a/samples-flatfoot/MusicPlayer/app/src/main/res/values/styles.xml b/samples-flatfoot/MusicPlayer/app/src/main/res/values/styles.xml
new file mode 100644
index 0000000..16dbab3
--- /dev/null
+++ b/samples-flatfoot/MusicPlayer/app/src/main/res/values/styles.xml
@@ -0,0 +1,17 @@
+<resources>
+
+    <!-- Base application theme. -->
+    <style name="AppTheme" parent="Theme.AppCompat.Light.DarkActionBar">
+        <!-- Customize your theme here. -->
+        <item name="colorPrimary">@color/colorPrimary</item>
+        <item name="colorPrimaryDark">@color/colorPrimaryDark</item>
+        <item name="colorAccent">@color/colorAccent</item>
+    </style>
+    <style name="AppTheme.NoActionBar">
+        <item name="windowActionBar">false</item>
+        <item name="windowNoTitle">true</item>
+    </style>
+    <style name="AppTheme.AppBarOverlay" parent="ThemeOverlay.AppCompat.Dark.ActionBar"/>
+    <style name="AppTheme.PopupOverlay" parent="ThemeOverlay.AppCompat.Light"/>
+
+</resources>
diff --git a/samples-flatfoot/MusicPlayer/build.gradle b/samples-flatfoot/MusicPlayer/build.gradle
new file mode 100644
index 0000000..5d6aefc
--- /dev/null
+++ b/samples-flatfoot/MusicPlayer/build.gradle
@@ -0,0 +1,32 @@
+// Top-level build file where you can add configuration options common to all sub-projects/modules.
+
+buildscript {
+    repositories {
+        jcenter()
+    }
+    dependencies {
+        classpath 'com.android.tools.build:gradle:2.2.3'
+
+        // NOTE: Do not place your application dependencies here; they belong
+        // in the individual module build.gradle files
+    }
+}
+
+ext.flatfootVersion = "1.0-SNAPSHOT"
+ext.supportLibVersion = "26.0.0-SNAPSHOT"
+
+allprojects {
+    repositories {
+        jcenter()
+        maven {
+            url "file://Volumes/android/appToolkitRepository/"
+        }
+        maven {
+            url "file://Volumes/android/m2repository/"
+        }
+    }
+}
+
+task clean(type: Delete) {
+    delete rootProject.buildDir
+}
diff --git a/samples-flatfoot/MusicPlayer/gradle.properties b/samples-flatfoot/MusicPlayer/gradle.properties
new file mode 100644
index 0000000..aac7c9b
--- /dev/null
+++ b/samples-flatfoot/MusicPlayer/gradle.properties
@@ -0,0 +1,17 @@
+# Project-wide Gradle settings.
+
+# IDE (e.g. Android Studio) users:
+# Gradle settings configured through the IDE *will override*
+# any settings specified in this file.
+
+# For more details on how to configure your build environment visit
+# http://www.gradle.org/docs/current/userguide/build_environment.html
+
+# Specifies the JVM arguments used for the daemon process.
+# The setting is particularly useful for tweaking memory settings.
+org.gradle.jvmargs=-Xmx1536m
+
+# When configured, Gradle will run in incubating parallel mode.
+# This option should only be used with decoupled projects. More details, visit
+# http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects
+# org.gradle.parallel=true
diff --git a/samples-flatfoot/MusicPlayer/gradle/wrapper/gradle-wrapper.jar b/samples-flatfoot/MusicPlayer/gradle/wrapper/gradle-wrapper.jar
new file mode 100644
index 0000000..13372ae
--- /dev/null
+++ b/samples-flatfoot/MusicPlayer/gradle/wrapper/gradle-wrapper.jar
Binary files differ
diff --git a/samples-flatfoot/MusicPlayer/gradle/wrapper/gradle-wrapper.properties b/samples-flatfoot/MusicPlayer/gradle/wrapper/gradle-wrapper.properties
new file mode 100644
index 0000000..04e285f
--- /dev/null
+++ b/samples-flatfoot/MusicPlayer/gradle/wrapper/gradle-wrapper.properties
@@ -0,0 +1,6 @@
+#Mon Dec 28 10:00:20 PST 2015
+distributionBase=GRADLE_USER_HOME
+distributionPath=wrapper/dists
+zipStoreBase=GRADLE_USER_HOME
+zipStorePath=wrapper/dists
+distributionUrl=https\://services.gradle.org/distributions/gradle-2.14.1-all.zip
diff --git a/samples-flatfoot/MusicPlayer/gradlew b/samples-flatfoot/MusicPlayer/gradlew
new file mode 100755
index 0000000..9d82f78
--- /dev/null
+++ b/samples-flatfoot/MusicPlayer/gradlew
@@ -0,0 +1,160 @@
+#!/usr/bin/env bash
+
+##############################################################################
+##
+##  Gradle start up script for UN*X
+##
+##############################################################################
+
+# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
+DEFAULT_JVM_OPTS=""
+
+APP_NAME="Gradle"
+APP_BASE_NAME=`basename "$0"`
+
+# Use the maximum available, or set MAX_FD != -1 to use that value.
+MAX_FD="maximum"
+
+warn ( ) {
+    echo "$*"
+}
+
+die ( ) {
+    echo
+    echo "$*"
+    echo
+    exit 1
+}
+
+# OS specific support (must be 'true' or 'false').
+cygwin=false
+msys=false
+darwin=false
+case "`uname`" in
+  CYGWIN* )
+    cygwin=true
+    ;;
+  Darwin* )
+    darwin=true
+    ;;
+  MINGW* )
+    msys=true
+    ;;
+esac
+
+# Attempt to set APP_HOME
+# Resolve links: $0 may be a link
+PRG="$0"
+# Need this for relative symlinks.
+while [ -h "$PRG" ] ; do
+    ls=`ls -ld "$PRG"`
+    link=`expr "$ls" : '.*-> \(.*\)$'`
+    if expr "$link" : '/.*' > /dev/null; then
+        PRG="$link"
+    else
+        PRG=`dirname "$PRG"`"/$link"
+    fi
+done
+SAVED="`pwd`"
+cd "`dirname \"$PRG\"`/" >/dev/null
+APP_HOME="`pwd -P`"
+cd "$SAVED" >/dev/null
+
+CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
+
+# Determine the Java command to use to start the JVM.
+if [ -n "$JAVA_HOME" ] ; then
+    if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
+        # IBM's JDK on AIX uses strange locations for the executables
+        JAVACMD="$JAVA_HOME/jre/sh/java"
+    else
+        JAVACMD="$JAVA_HOME/bin/java"
+    fi
+    if [ ! -x "$JAVACMD" ] ; then
+        die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
+
+Please set the JAVA_HOME variable in your environment to match the
+location of your Java installation."
+    fi
+else
+    JAVACMD="java"
+    which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
+
+Please set the JAVA_HOME variable in your environment to match the
+location of your Java installation."
+fi
+
+# Increase the maximum file descriptors if we can.
+if [ "$cygwin" = "false" -a "$darwin" = "false" ] ; then
+    MAX_FD_LIMIT=`ulimit -H -n`
+    if [ $? -eq 0 ] ; then
+        if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then
+            MAX_FD="$MAX_FD_LIMIT"
+        fi
+        ulimit -n $MAX_FD
+        if [ $? -ne 0 ] ; then
+            warn "Could not set maximum file descriptor limit: $MAX_FD"
+        fi
+    else
+        warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT"
+    fi
+fi
+
+# For Darwin, add options to specify how the application appears in the dock
+if $darwin; then
+    GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\""
+fi
+
+# For Cygwin, switch paths to Windows format before running java
+if $cygwin ; then
+    APP_HOME=`cygpath --path --mixed "$APP_HOME"`
+    CLASSPATH=`cygpath --path --mixed "$CLASSPATH"`
+    JAVACMD=`cygpath --unix "$JAVACMD"`
+
+    # We build the pattern for arguments to be converted via cygpath
+    ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null`
+    SEP=""
+    for dir in $ROOTDIRSRAW ; do
+        ROOTDIRS="$ROOTDIRS$SEP$dir"
+        SEP="|"
+    done
+    OURCYGPATTERN="(^($ROOTDIRS))"
+    # Add a user-defined pattern to the cygpath arguments
+    if [ "$GRADLE_CYGPATTERN" != "" ] ; then
+        OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)"
+    fi
+    # Now convert the arguments - kludge to limit ourselves to /bin/sh
+    i=0
+    for arg in "$@" ; do
+        CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -`
+        CHECK2=`echo "$arg"|egrep -c "^-"`                                 ### Determine if an option
+
+        if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then                    ### Added a condition
+            eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"`
+        else
+            eval `echo args$i`="\"$arg\""
+        fi
+        i=$((i+1))
+    done
+    case $i in
+        (0) set -- ;;
+        (1) set -- "$args0" ;;
+        (2) set -- "$args0" "$args1" ;;
+        (3) set -- "$args0" "$args1" "$args2" ;;
+        (4) set -- "$args0" "$args1" "$args2" "$args3" ;;
+        (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;;
+        (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;;
+        (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;;
+        (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;;
+        (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;;
+    esac
+fi
+
+# Split up the JVM_OPTS And GRADLE_OPTS values into an array, following the shell quoting and substitution rules
+function splitJvmOpts() {
+    JVM_OPTS=("$@")
+}
+eval splitJvmOpts $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS
+JVM_OPTS[${#JVM_OPTS[*]}]="-Dorg.gradle.appname=$APP_BASE_NAME"
+
+exec "$JAVACMD" "${JVM_OPTS[@]}" -classpath "$CLASSPATH" org.gradle.wrapper.GradleWrapperMain "$@"
diff --git a/samples-flatfoot/MusicPlayer/gradlew.bat b/samples-flatfoot/MusicPlayer/gradlew.bat
new file mode 100644
index 0000000..aec9973
--- /dev/null
+++ b/samples-flatfoot/MusicPlayer/gradlew.bat
@@ -0,0 +1,90 @@
+@if "%DEBUG%" == "" @echo off

+@rem ##########################################################################

+@rem

+@rem  Gradle startup script for Windows

+@rem

+@rem ##########################################################################

+

+@rem Set local scope for the variables with windows NT shell

+if "%OS%"=="Windows_NT" setlocal

+

+@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.

+set DEFAULT_JVM_OPTS=

+

+set DIRNAME=%~dp0

+if "%DIRNAME%" == "" set DIRNAME=.

+set APP_BASE_NAME=%~n0

+set APP_HOME=%DIRNAME%

+

+@rem Find java.exe

+if defined JAVA_HOME goto findJavaFromJavaHome

+

+set JAVA_EXE=java.exe

+%JAVA_EXE% -version >NUL 2>&1

+if "%ERRORLEVEL%" == "0" goto init

+

+echo.

+echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.

+echo.

+echo Please set the JAVA_HOME variable in your environment to match the

+echo location of your Java installation.

+

+goto fail

+

+:findJavaFromJavaHome

+set JAVA_HOME=%JAVA_HOME:"=%

+set JAVA_EXE=%JAVA_HOME%/bin/java.exe

+

+if exist "%JAVA_EXE%" goto init

+

+echo.

+echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%

+echo.

+echo Please set the JAVA_HOME variable in your environment to match the

+echo location of your Java installation.

+

+goto fail

+

+:init

+@rem Get command-line arguments, handling Windowz variants

+

+if not "%OS%" == "Windows_NT" goto win9xME_args

+if "%@eval[2+2]" == "4" goto 4NT_args

+

+:win9xME_args

+@rem Slurp the command line arguments.

+set CMD_LINE_ARGS=

+set _SKIP=2

+

+:win9xME_args_slurp

+if "x%~1" == "x" goto execute

+

+set CMD_LINE_ARGS=%*

+goto execute

+

+:4NT_args

+@rem Get arguments from the 4NT Shell from JP Software

+set CMD_LINE_ARGS=%$

+

+:execute

+@rem Setup the command line

+

+set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar

+

+@rem Execute Gradle

+"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS%

+

+:end

+@rem End local scope for the variables with windows NT shell

+if "%ERRORLEVEL%"=="0" goto mainEnd

+

+:fail

+rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of

+rem the _cmd.exe /c_ return code!

+if  not "" == "%GRADLE_EXIT_CONSOLE%" exit 1

+exit /b 1

+

+:mainEnd

+if "%OS%"=="Windows_NT" endlocal

+

+:omega

diff --git a/samples-flatfoot/MusicPlayer/settings.gradle b/samples-flatfoot/MusicPlayer/settings.gradle
new file mode 100644
index 0000000..e7b4def
--- /dev/null
+++ b/samples-flatfoot/MusicPlayer/settings.gradle
@@ -0,0 +1 @@
+include ':app'
diff --git a/samples-flatfoot/PersistenceSample/.gitignore b/samples-flatfoot/PersistenceSample/.gitignore
new file mode 100644
index 0000000..c33eb5b
--- /dev/null
+++ b/samples-flatfoot/PersistenceSample/.gitignore
@@ -0,0 +1,9 @@
+*.iml
+.gradle
+/local.properties
+/.idea/workspace.xml
+/.idea/libraries
+.DS_Store
+build/
+/captures
+.externalNativeBuild
diff --git a/samples-flatfoot/PersistenceSample/CONTRIBUTING.md b/samples-flatfoot/PersistenceSample/CONTRIBUTING.md
new file mode 100644
index 0000000..7b86f95
--- /dev/null
+++ b/samples-flatfoot/PersistenceSample/CONTRIBUTING.md
@@ -0,0 +1,35 @@
+# How to become a contributor and submit your own code
+
+## Contributor License Agreements
+
+We'd love to accept your sample apps and patches! Before we can take them, we
+have to jump a couple of legal hurdles.
+
+Please fill out either the individual or corporate Contributor License Agreement (CLA).
+
+  * If you are an individual writing original source code and you're sure you
+    own the intellectual property, then you'll need to sign an [individual CLA]
+    (https://cla.developers.google.com).
+  * If you work for a company that wants to allow you to contribute your work,
+    then you'll need to sign a [corporate CLA]
+    (https://cla.developers.google.com).
+  * Please make sure you sign both, Android and Google CLA
+
+Follow either of the two links above to access the appropriate CLA and
+instructions for how to sign and return it. Once we receive it, we'll be able to
+accept your pull requests.
+
+## Contributing A Patch
+
+1. Submit an issue describing your proposed change to the repo in question.
+1. The repo owner will respond to your issue promptly.
+1. If your proposed change is accepted, and you haven't already done so, sign a
+   Contributor License Agreement (see details above).
+1. Fork the desired repo, develop and test your code changes.
+1. Ensure that your code adheres to the existing style in the sample to which
+   you are contributing. Refer to the
+   [Android Code Style Guide]
+   (https://source.android.com/source/code-style.html) for the
+   recommended coding standards for this organization.
+1. Ensure that your code has an appropriate set of unit tests which all pass.
+1. Submit a pull request.
diff --git a/samples-flatfoot/PersistenceSample/LICENSE b/samples-flatfoot/PersistenceSample/LICENSE
new file mode 100644
index 0000000..1af981f
--- /dev/null
+++ b/samples-flatfoot/PersistenceSample/LICENSE
@@ -0,0 +1,201 @@
+                                 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
+
+   APPENDIX: How to apply the Apache License to your work.
+
+      To apply the Apache License to your work, attach the following
+      boilerplate notice, with the fields enclosed by brackets "[]"
+      replaced with your own identifying information. (Don't include
+      the brackets!)  The text should be enclosed in the appropriate
+      comment syntax for the file format. We also recommend that a
+      file or class name and description of purpose be included on the
+      same "printed page" as the copyright notice for easier
+      identification within third-party archives.
+
+   Copyright 2014 The Android Open Source Project
+
+   Licensed under the Apache License, Version 2.0 (the "License");
+   you may not use this file except in compliance with the License.
+   You may obtain a copy of the License at
+
+       http://www.apache.org/licenses/LICENSE-2.0
+
+   Unless required by applicable law or agreed to in writing, software
+   distributed under the License is distributed on an "AS IS" BASIS,
+   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+   See the License for the specific language governing permissions and
+   limitations under the License.
diff --git a/samples-flatfoot/PersistenceSample/app/build.gradle b/samples-flatfoot/PersistenceSample/app/build.gradle
new file mode 100644
index 0000000..41b73e6
--- /dev/null
+++ b/samples-flatfoot/PersistenceSample/app/build.gradle
@@ -0,0 +1,76 @@
+/*
+ * Copyright 2017, The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+apply plugin: 'com.android.application'
+
+android {
+    compileSdkVersion 25
+    buildToolsVersion rootProject.buildToolsVersion
+    defaultConfig {
+        applicationId 'com.example.android.persistence'
+        minSdkVersion 21
+        targetSdkVersion 25
+        versionCode 1
+        versionName "1.0"
+        testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
+    }
+
+    buildTypes {
+        release {
+            minifyEnabled false
+            proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
+        }
+    }
+    dataBinding {
+        enabled = true
+    }
+    productFlavors {
+    }
+
+    lintOptions {
+        abortOnError false
+    }
+
+}
+
+dependencies {
+    compile fileTree(include: ['*.jar'], dir: 'libs')
+    compile 'com.android.support:appcompat-v7:' + rootProject.supportLibVersion;
+    compile 'com.android.support:cardview-v7:' + rootProject.supportLibVersion;
+    compile 'com.android.support:recyclerview-v7:' + rootProject.supportLibVersion;
+    compile 'android.arch.lifecycle:extensions:1.0-SNAPSHOT'
+    compile 'android.arch.persistence.room:runtime:1.0-SNAPSHOT'
+    annotationProcessor "android.arch.lifecycle:compiler:1.0-SNAPSHOT"
+    annotationProcessor "android.arch.persistence.room:compiler:1.0-SNAPSHOT"
+
+    testCompile 'junit:junit:4.12'
+
+    // Testing-only dependencies
+    androidTestCompile 'com.android.support.test:runner:' + rootProject.runnerVersion;
+    androidTestCompile 'com.android.support.test:rules:' + rootProject.rulesVersion;
+    androidTestCompile 'com.android.support.test.espresso:espresso-core:' + rootProject.espressoVersion;
+
+    androidTestCompile ('com.android.support.test.espresso:espresso-contrib:2.2'){
+        exclude group: 'com.android.support', module: 'appcompat-v7'
+        exclude group: 'com.android.support', module: 'support-v4'
+        exclude module: 'recyclerview-v7'
+    }
+
+    // Force usage of dependencies in the test app, since it is internally used by the runner module.
+    androidTestCompile 'com.android.support:support-annotations:' + rootProject.supportLibVersion;
+    androidTestCompile 'com.android.support:support-v4:' + rootProject.supportLibVersion;
+    androidTestCompile 'com.android.support:recyclerview-v7:' + rootProject.supportLibVersion;
+}
\ No newline at end of file
diff --git a/samples-flatfoot/PersistenceSample/app/proguard-rules.pro b/samples-flatfoot/PersistenceSample/app/proguard-rules.pro
new file mode 100644
index 0000000..4cb7103
--- /dev/null
+++ b/samples-flatfoot/PersistenceSample/app/proguard-rules.pro
@@ -0,0 +1,17 @@
+# Add project specific ProGuard rules here.
+# By default, the flags in this file are appended to flags specified
+# in /usr/local/google/home/jalc/sw/android-sdks/android-sdk-linux/tools/proguard/proguard-android.txt
+# You can edit the include path and order by changing the proguardFiles
+# directive in build.gradle.
+#
+# For more details, see
+#   http://developer.android.com/guide/developing/tools/proguard.html
+
+# Add any project specific keep options here:
+
+# If your project uses WebView with JS, uncomment the following
+# and specify the fully qualified class name to the JavaScript interface
+# class:
+#-keepclassmembers class fqcn.of.javascript.interface.for.webview {
+#   public *;
+#}
diff --git a/samples-flatfoot/PersistenceSample/app/src/androidTest/java/com/example/android/persistence/MainActivityTest.java b/samples-flatfoot/PersistenceSample/app/src/androidTest/java/com/example/android/persistence/MainActivityTest.java
new file mode 100644
index 0000000..b9b899c
--- /dev/null
+++ b/samples-flatfoot/PersistenceSample/app/src/androidTest/java/com/example/android/persistence/MainActivityTest.java
@@ -0,0 +1,132 @@
+/*
+ * Copyright 2017, The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES 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.persistence;
+
+
+import android.arch.lifecycle.Observer;
+import android.arch.lifecycle.ViewModelProviders;
+import android.support.annotation.Nullable;
+import android.support.test.espresso.Espresso;
+import android.support.test.espresso.IdlingResource;
+import android.support.test.espresso.contrib.RecyclerViewActions;
+import android.support.test.rule.ActivityTestRule;
+import android.support.v4.app.Fragment;
+
+import com.example.android.persistence.db.entity.ProductEntity;
+import com.example.android.persistence.viewmodel.ProductListViewModel;
+
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+
+import java.util.List;
+import java.util.concurrent.atomic.AtomicBoolean;
+
+import static android.support.test.espresso.Espresso.onView;
+import static android.support.test.espresso.action.ViewActions.click;
+import static android.support.test.espresso.assertion.ViewAssertions.matches;
+import static android.support.test.espresso.matcher.ViewMatchers.isDisplayed;
+import static android.support.test.espresso.matcher.ViewMatchers.withContentDescription;
+import static android.support.test.espresso.matcher.ViewMatchers.withText;
+import static org.hamcrest.core.IsNot.not;
+
+public class MainActivityTest {
+
+    @Rule
+    public ActivityTestRule<MainActivity> mActivityRule = new ActivityTestRule<>(
+            MainActivity.class);
+
+    private SimpleIdlingResource idlingRes = new SimpleIdlingResource();
+
+    @Before
+    public void idlingResourceSetup() {
+
+        Espresso.registerIdlingResources(idlingRes);
+        // There's always
+        idlingRes.setIdleNow(false);
+
+        ProductListViewModel productListViewModel = getProductListViewModel();
+
+        // Subscribe to ProductListViewModel's products list observable to figure out when the
+        // app is idle.
+        productListViewModel.getProducts().observeForever(new Observer<List<ProductEntity>>() {
+            @Override
+            public void onChanged(@Nullable List<ProductEntity> productEntities) {
+                if (productEntities != null) {
+                    idlingRes.setIdleNow(true);
+                }
+            }
+        });
+    }
+
+    @Test
+    public void clickOnFirstItem_opensComments() {
+        // When clicking on the first product
+        onView(withContentDescription(R.string.cd_products_list))
+                .perform(RecyclerViewActions.actionOnItemAtPosition(0, click()));
+
+        // Then the second screen with the comments should appear.
+        onView(withContentDescription(R.string.cd_comments_list))
+                .check(matches(isDisplayed()));
+
+        // Then the second screen with the comments should appear.
+        onView(withContentDescription(R.string.cd_product_name))
+                .check(matches(not(withText(""))));
+
+    }
+
+    /** Gets the ViewModel for the current fragment */
+    private ProductListViewModel getProductListViewModel() {
+        MainActivity activity = mActivityRule.getActivity();
+
+        Fragment productListFragment = activity.getSupportFragmentManager()
+                .findFragmentByTag(ProductListFragment.TAG);
+
+        return ViewModelProviders.of(productListFragment)
+                .get(ProductListViewModel.class);
+    }
+
+    private static class SimpleIdlingResource implements IdlingResource {
+
+        // written from main thread, read from any thread.
+        private volatile ResourceCallback mResourceCallback;
+
+        private AtomicBoolean mIsIdleNow = new AtomicBoolean(true);
+
+        public void setIdleNow(boolean idleNow) {
+            mIsIdleNow.set(idleNow);
+            if (idleNow) {
+                mResourceCallback.onTransitionToIdle();
+            }
+        }
+
+        @Override
+        public String getName() {
+            return "Simple idling resource";
+        }
+
+        @Override
+        public boolean isIdleNow() {
+            return mIsIdleNow.get();
+        }
+
+        @Override
+        public void registerIdleTransitionCallback(ResourceCallback callback) {
+            mResourceCallback = callback;
+        }
+    }
+}
\ No newline at end of file
diff --git a/samples-flatfoot/PersistenceSample/app/src/main/AndroidManifest.xml b/samples-flatfoot/PersistenceSample/app/src/main/AndroidManifest.xml
new file mode 100644
index 0000000..322d957
--- /dev/null
+++ b/samples-flatfoot/PersistenceSample/app/src/main/AndroidManifest.xml
@@ -0,0 +1,40 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  ~ Copyright 2017, The Android Open Source Project
+  ~
+  ~ Licensed under the Apache License, Version 2.0 (the "License");
+  ~ you may not use this file except in compliance with the License.
+  ~ You may obtain a copy of the License at
+  ~
+  ~      http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing, software
+  ~ distributed under the License is distributed on an "AS IS" BASIS,
+  ~ WITHOUT 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.persistence">
+
+    <uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
+    <uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" />
+
+    <uses-feature android:name="android.hardware.location.gps" />
+
+    <application
+        android:allowBackup="false"
+        android:icon="@mipmap/ic_launcher"
+        android:label="@string/app_name"
+        android:supportsRtl="true"
+        android:theme="@style/AppTheme">
+        <activity android:name="com.example.android.persistence.MainActivity">
+            <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-flatfoot/PersistenceSample/app/src/main/java/com/example/android/persistence/MainActivity.java b/samples-flatfoot/PersistenceSample/app/src/main/java/com/example/android/persistence/MainActivity.java
new file mode 100644
index 0000000..aee8255
--- /dev/null
+++ b/samples-flatfoot/PersistenceSample/app/src/main/java/com/example/android/persistence/MainActivity.java
@@ -0,0 +1,51 @@
+/*
+ * Copyright 2017, The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES 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.persistence;
+
+import android.os.Bundle;
+import android.support.annotation.Nullable;
+
+import android.arch.lifecycle.LifecycleActivity;
+import com.example.android.persistence.model.Product;
+
+public class MainActivity extends LifecycleActivity {
+    @Override
+    protected void onCreate(@Nullable Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+        setContentView(R.layout.main_activity);
+
+        // Add product list fragment if this is first creation
+        if (savedInstanceState == null) {
+            ProductListFragment fragment = new ProductListFragment();
+
+            getSupportFragmentManager().beginTransaction()
+                    .add(R.id.fragment_container, fragment, ProductListFragment.TAG).commit();
+        }
+    }
+
+    /** Shows the product detail fragment */
+    public void show(Product product) {
+
+        ProductFragment productFragment = ProductFragment.forProduct(product.getId());
+
+        getSupportFragmentManager()
+                .beginTransaction()
+                .addToBackStack("product")
+                .replace(R.id.fragment_container,
+                        productFragment, null).commit();
+    }
+}
diff --git a/samples-flatfoot/PersistenceSample/app/src/main/java/com/example/android/persistence/ProductFragment.java b/samples-flatfoot/PersistenceSample/app/src/main/java/com/example/android/persistence/ProductFragment.java
new file mode 100644
index 0000000..53dbcbb
--- /dev/null
+++ b/samples-flatfoot/PersistenceSample/app/src/main/java/com/example/android/persistence/ProductFragment.java
@@ -0,0 +1,112 @@
+/*
+ * Copyright 2017, The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES 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.persistence;
+
+import android.arch.lifecycle.LifecycleFragment;
+import android.arch.lifecycle.Observer;
+import android.arch.lifecycle.ViewModelProviders;
+import android.databinding.DataBindingUtil;
+import android.os.Bundle;
+import android.support.annotation.Nullable;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+
+import com.example.android.persistence.databinding.ProductFragmentBinding;
+import com.example.android.persistence.db.entity.CommentEntity;
+import com.example.android.persistence.db.entity.ProductEntity;
+import com.example.android.persistence.model.Comment;
+import com.example.android.persistence.ui.CommentAdapter;
+import com.example.android.persistence.ui.CommentClickCallback;
+import com.example.android.persistence.viewmodel.ProductViewModel;
+
+import java.util.List;
+
+public class ProductFragment extends LifecycleFragment {
+
+    private static final String KEY_PRODUCT_ID = "product_id";
+
+    private ProductFragmentBinding mBinding;
+
+    private CommentAdapter mCommentAdapter;
+
+    @Nullable
+    @Override
+    public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container,
+            @Nullable Bundle savedInstanceState) {
+        // Inflate this data binding layout
+        mBinding = DataBindingUtil.inflate(inflater, R.layout.product_fragment, container, false);
+
+        // Create and set the adapter for the RecyclerView.
+        mCommentAdapter = new CommentAdapter(mCommentClickCallback);
+        mBinding.commentList.setAdapter(mCommentAdapter);
+        return mBinding.getRoot();
+    }
+
+    private final CommentClickCallback mCommentClickCallback = new CommentClickCallback() {
+        @Override
+        public void onClick(Comment comment) {
+            // no-op
+
+        }
+    };
+
+    @Override
+    public void onActivityCreated(@Nullable Bundle savedInstanceState) {
+        super.onActivityCreated(savedInstanceState);
+        ProductViewModel.Factory factory = new ProductViewModel.Factory(
+                getActivity().getApplication(), getArguments().getInt(KEY_PRODUCT_ID));
+
+        final ProductViewModel model = ViewModelProviders.of(this, factory)
+                .get(ProductViewModel.class);
+
+        mBinding.setProductViewModel(model);
+
+        subscribeToModel(model);
+    }
+
+    private void subscribeToModel(final ProductViewModel model) {
+
+        model.getComments().observe(this, new Observer<List<CommentEntity>>() {
+                    @Override
+                    public void onChanged(@Nullable List<CommentEntity> commentEntities) {
+                        if (commentEntities != null) {
+                            mBinding.setIsLoading(false);
+                            mCommentAdapter.setCommentList(commentEntities);
+                        } else {
+                            mBinding.setIsLoading(true);
+                        }
+                    }
+                });
+
+                model.getObservableProduct().observe(this, new Observer<ProductEntity>() {
+                    @Override
+                    public void onChanged(@Nullable ProductEntity productEntity) {
+                        model.setProduct(productEntity);
+                    }
+                });
+    }
+
+    /** Creates product fragment for specific product ID */
+    public static ProductFragment forProduct(int productId) {
+        ProductFragment fragment = new ProductFragment();
+        Bundle args = new Bundle();
+        args.putInt(KEY_PRODUCT_ID, productId);
+        fragment.setArguments(args);
+        return fragment;
+    }
+}
diff --git a/samples-flatfoot/PersistenceSample/app/src/main/java/com/example/android/persistence/ProductListFragment.java b/samples-flatfoot/PersistenceSample/app/src/main/java/com/example/android/persistence/ProductListFragment.java
new file mode 100644
index 0000000..772bc9c
--- /dev/null
+++ b/samples-flatfoot/PersistenceSample/app/src/main/java/com/example/android/persistence/ProductListFragment.java
@@ -0,0 +1,92 @@
+/*
+ * Copyright 2017, The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES 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.persistence;
+
+import android.arch.lifecycle.Lifecycle;
+import android.arch.lifecycle.LifecycleFragment;
+import android.arch.lifecycle.Observer;
+import android.arch.lifecycle.ViewModelProviders;
+import android.databinding.DataBindingUtil;
+import android.os.Bundle;
+import android.support.annotation.Nullable;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+
+import com.example.android.persistence.databinding.ListFragmentBinding;
+import com.example.android.persistence.db.entity.ProductEntity;
+import com.example.android.persistence.model.Product;
+import com.example.android.persistence.ui.ProductAdapter;
+import com.example.android.persistence.ui.ProductClickCallback;
+import com.example.android.persistence.viewmodel.ProductListViewModel;
+
+import java.util.List;
+
+public class ProductListFragment extends LifecycleFragment {
+
+    public static final String TAG = "ProductListViewModel";
+
+    private ProductAdapter mProductAdapter;
+
+    private ListFragmentBinding mBinding;
+
+    @Nullable
+    @Override
+    public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container,
+            @Nullable Bundle savedInstanceState) {
+        mBinding = DataBindingUtil.inflate(inflater, R.layout.list_fragment, container, false);
+
+        mProductAdapter = new ProductAdapter(mProductClickCallback);
+        mBinding.productsList.setAdapter(mProductAdapter);
+
+        return mBinding.getRoot();
+    }
+
+    @Override
+    public void onActivityCreated(@Nullable Bundle savedInstanceState) {
+        super.onActivityCreated(savedInstanceState);
+        final ProductListViewModel viewModel =
+                ViewModelProviders.of(this).get(ProductListViewModel.class);
+
+        subscribeUi(viewModel);
+    }
+
+    private void subscribeUi(ProductListViewModel viewModel) {
+        // Update the list when the data changes
+        viewModel.getProducts().observe(this, new Observer<List<ProductEntity>>() {
+            @Override
+            public void onChanged(@Nullable List<ProductEntity> myProducts) {
+                if (myProducts != null) {
+                    mBinding.setIsLoading(false);
+                    mProductAdapter.setProductList(myProducts);
+                } else {
+                    mBinding.setIsLoading(true);
+                }
+            }
+        });
+    }
+
+    private final ProductClickCallback mProductClickCallback = new ProductClickCallback() {
+        @Override
+        public void onClick(Product product) {
+
+            if (getLifecycle().getCurrentState().isAtLeast(Lifecycle.State.STARTED)) {
+                ((MainActivity) getActivity()).show(product);
+            }
+        }
+    };
+}
diff --git a/samples-flatfoot/PersistenceSample/app/src/main/java/com/example/android/persistence/db/AppDatabase.java b/samples-flatfoot/PersistenceSample/app/src/main/java/com/example/android/persistence/db/AppDatabase.java
new file mode 100644
index 0000000..ce0afe4
--- /dev/null
+++ b/samples-flatfoot/PersistenceSample/app/src/main/java/com/example/android/persistence/db/AppDatabase.java
@@ -0,0 +1,38 @@
+/*
+ * Copyright 2017, The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES 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.persistence.db;
+
+import android.arch.persistence.room.Database;
+import android.arch.persistence.room.RoomDatabase;
+import android.arch.persistence.room.TypeConverters;
+
+import com.example.android.persistence.db.dao.CommentDao;
+import com.example.android.persistence.db.dao.ProductDao;
+import com.example.android.persistence.db.entity.CommentEntity;
+import com.example.android.persistence.db.entity.ProductEntity;
+import com.example.android.persistence.db.converter.DateConverter;
+
+@Database(entities = {ProductEntity.class, CommentEntity.class}, version = 1)
+@TypeConverters(DateConverter.class)
+public abstract class AppDatabase extends RoomDatabase {
+
+    static final String DATABASE_NAME = "basic-sample-db";
+
+    public abstract ProductDao productDao();
+
+    public abstract CommentDao commentDao();
+}
diff --git a/samples-flatfoot/PersistenceSample/app/src/main/java/com/example/android/persistence/db/DatabaseCreator.java b/samples-flatfoot/PersistenceSample/app/src/main/java/com/example/android/persistence/db/DatabaseCreator.java
new file mode 100644
index 0000000..5ecb884
--- /dev/null
+++ b/samples-flatfoot/PersistenceSample/app/src/main/java/com/example/android/persistence/db/DatabaseCreator.java
@@ -0,0 +1,123 @@
+/*
+ * Copyright 2017, The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES 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.persistence.db;
+
+import android.arch.lifecycle.LiveData;
+import android.arch.lifecycle.MutableLiveData;
+import android.arch.persistence.room.Room;
+import android.content.Context;
+import android.os.AsyncTask;
+import android.support.annotation.Nullable;
+import android.util.Log;
+
+import java.util.concurrent.atomic.AtomicBoolean;
+
+import static com.example.android.persistence.db.AppDatabase.DATABASE_NAME;
+
+/**
+ * Creates the {@link AppDatabase} asynchronously, exposing a LiveData object to notify of creation.
+ */
+public class DatabaseCreator {
+
+    private static DatabaseCreator sInstance;
+
+    private final MutableLiveData<Boolean> mIsDatabaseCreated = new MutableLiveData<>();
+
+    private AppDatabase mDb;
+
+    private final AtomicBoolean mInitializing = new AtomicBoolean(true);
+
+    // For Singleton instantiation
+    private static final Object LOCK = new Object();
+
+    public synchronized static DatabaseCreator getInstance(Context context) {
+        if (sInstance == null) {
+            synchronized (LOCK) {
+                if (sInstance == null) {
+                    sInstance = new DatabaseCreator();
+                }
+            }
+        }
+        return sInstance;
+    }
+
+    /** Used to observe when the database initialization is done */
+    public LiveData<Boolean> isDatabaseCreated() {
+        return mIsDatabaseCreated;
+    }
+
+    @Nullable
+    public AppDatabase getDatabase() {
+        return mDb;
+    }
+
+    /**
+     * Creates or returns a previously-created database.
+     * <p>
+     * Although this uses an AsyncTask which currently uses a serial executor, it's thread-safe.
+     */
+    public void createDb(Context context) {
+
+        Log.d("DatabaseCreator", "Creating DB from " + Thread.currentThread().getName());
+
+        if (!mInitializing.compareAndSet(true, false)) {
+            return; // Already initializing
+        }
+
+        mIsDatabaseCreated.setValue(false);// Trigger an update to show a loading screen.
+        new AsyncTask<Context, Void, Void>() {
+
+            @Override
+            protected Void doInBackground(Context... params) {
+                Log.d("DatabaseCreator",
+                        "Starting bg job " + Thread.currentThread().getName());
+
+                Context context = params[0].getApplicationContext();
+
+                // Reset the database to have new data on every run.
+                context.deleteDatabase(DATABASE_NAME);
+
+                // Build the database!
+                AppDatabase db = Room.databaseBuilder(context.getApplicationContext(),
+                        AppDatabase.class, DATABASE_NAME).build();
+
+                // Add a delay to simulate a long-running operation
+                addDelay();
+
+                // Add some data to the database
+                DatabaseInitUtil.initializeDb(db);
+                Log.d("DatabaseCreator",
+                        "DB was populated in thread " + Thread.currentThread().getName());
+
+                mDb = db;
+                return null;
+            }
+
+            @Override
+            protected void onPostExecute(Void ignored) {
+                // Now on the main thread, notify observers that the db is created and ready.
+                mIsDatabaseCreated.setValue(true);
+            }
+        }.execute(context.getApplicationContext());
+    }
+
+    private void addDelay() {
+        try {
+            Thread.sleep(4000);
+        } catch (InterruptedException ignored) {}
+    }
+}
diff --git a/samples-flatfoot/PersistenceSample/app/src/main/java/com/example/android/persistence/db/DatabaseInitUtil.java b/samples-flatfoot/PersistenceSample/app/src/main/java/com/example/android/persistence/db/DatabaseInitUtil.java
new file mode 100644
index 0000000..c68fe8f
--- /dev/null
+++ b/samples-flatfoot/PersistenceSample/app/src/main/java/com/example/android/persistence/db/DatabaseInitUtil.java
@@ -0,0 +1,88 @@
+/*
+ * Copyright 2017, The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES 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.persistence.db;
+
+import com.example.android.persistence.db.entity.CommentEntity;
+import com.example.android.persistence.db.entity.ProductEntity;
+import com.example.android.persistence.model.Product;
+
+import java.util.ArrayList;
+import java.util.Date;
+import java.util.List;
+import java.util.Random;
+import java.util.concurrent.TimeUnit;
+
+/** Generates dummy data and inserts them into the database */
+class DatabaseInitUtil {
+
+    private static final String[] FIRST = new String[]{
+            "Special edition", "New", "Cheap", "Quality", "Used"};
+    private static final String[] SECOND = new String[]{
+            "Three-headed Monkey", "Rubber Chicken", "Pint of Grog", "Monocle"};
+    private static final String[] DESCRIPTION = new String[]{
+            "is finally here", "is recommended by Stan S. Stanman",
+            "is the best sold product on Mêlée Island", "is \uD83D\uDCAF", "is ❤️", "is fine"};
+    private static final String[] COMMENTS = new String[]{
+            "Comment 1", "Comment 2", "Comment 3", "Comment 4", "Comment 5", "Comment 6",
+    };
+
+    static void initializeDb(AppDatabase db) {
+        List<ProductEntity> products = new ArrayList<>(FIRST.length * SECOND.length);
+        List<CommentEntity> comments = new ArrayList<>();
+
+        generateData(products, comments);
+
+        insertData(db, products, comments);
+    }
+
+    private static void generateData(List<ProductEntity> products, List<CommentEntity> comments) {
+        Random rnd = new Random();
+        for (int i = 0; i < FIRST.length; i++) {
+            for (int j = 0; j < SECOND.length; j++) {
+                ProductEntity product = new ProductEntity();
+                product.setName(FIRST[i] + " " + SECOND[j]);
+                product.setDescription(product.getName() + " " + DESCRIPTION[j]);
+                product.setPrice(rnd.nextInt(240));
+                product.setId(FIRST.length * i + j + 1);
+                products.add(product);
+            }
+        }
+
+        for (Product product : products) {
+            int commentsNumber = rnd.nextInt(5) + 1;
+            for (int i = 0; i < commentsNumber; i++) {
+                CommentEntity comment = new CommentEntity();
+                comment.setProductId(product.getId());
+                comment.setText(COMMENTS[i] + " for " + product.getName());
+                comment.setPostedAt(new Date(System.currentTimeMillis()
+                        - TimeUnit.DAYS.toMillis(commentsNumber - i) + TimeUnit.HOURS.toMillis(i)));
+                comments.add(comment);
+            }
+        }
+    }
+
+    private static void insertData(AppDatabase db, List<ProductEntity> products, List<CommentEntity> comments) {
+        db.beginTransaction();
+        try {
+            db.productDao().insertAll(products);
+            db.commentDao().insertAll(comments);
+            db.setTransactionSuccessful();
+        } finally {
+            db.endTransaction();
+        }
+    }
+}
diff --git a/samples-flatfoot/PersistenceSample/app/src/main/java/com/example/android/persistence/db/converter/DateConverter.java b/samples-flatfoot/PersistenceSample/app/src/main/java/com/example/android/persistence/db/converter/DateConverter.java
new file mode 100644
index 0000000..da5fe22
--- /dev/null
+++ b/samples-flatfoot/PersistenceSample/app/src/main/java/com/example/android/persistence/db/converter/DateConverter.java
@@ -0,0 +1,33 @@
+/*
+ * Copyright 2017, The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES 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.persistence.db.converter;
+
+import android.arch.persistence.room.TypeConverter;
+
+import java.util.Date;
+
+public class DateConverter {
+    @TypeConverter
+    public static Date toDate(Long timestamp) {
+        return timestamp == null ? null : new Date(timestamp);
+    }
+
+    @TypeConverter
+    public static Long toTimestamp(Date date) {
+        return date == null ? null : date.getTime();
+    }
+}
diff --git a/samples-flatfoot/PersistenceSample/app/src/main/java/com/example/android/persistence/db/dao/CommentDao.java b/samples-flatfoot/PersistenceSample/app/src/main/java/com/example/android/persistence/db/dao/CommentDao.java
new file mode 100644
index 0000000..b351526
--- /dev/null
+++ b/samples-flatfoot/PersistenceSample/app/src/main/java/com/example/android/persistence/db/dao/CommentDao.java
@@ -0,0 +1,40 @@
+/*
+ * Copyright 2017, The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES 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.persistence.db.dao;
+
+
+import android.arch.lifecycle.LiveData;
+import android.arch.persistence.room.Dao;
+import android.arch.persistence.room.Insert;
+import android.arch.persistence.room.OnConflictStrategy;
+import android.arch.persistence.room.Query;
+
+import com.example.android.persistence.db.entity.CommentEntity;
+
+import java.util.List;
+
+@Dao
+public interface CommentDao {
+    @Query("SELECT * FROM comments where productId = :productId")
+    LiveData<List<CommentEntity>> loadComments(int productId);
+
+    @Query("SELECT * FROM comments where productId = :productId")
+    List<CommentEntity> loadCommentsSync(int productId);
+
+    @Insert(onConflict = OnConflictStrategy.REPLACE)
+    void insertAll(List<CommentEntity> products);
+}
diff --git a/samples-flatfoot/PersistenceSample/app/src/main/java/com/example/android/persistence/db/dao/ProductDao.java b/samples-flatfoot/PersistenceSample/app/src/main/java/com/example/android/persistence/db/dao/ProductDao.java
new file mode 100644
index 0000000..39407d06
--- /dev/null
+++ b/samples-flatfoot/PersistenceSample/app/src/main/java/com/example/android/persistence/db/dao/ProductDao.java
@@ -0,0 +1,42 @@
+/*
+ * Copyright 2017, The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES 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.persistence.db.dao;
+
+import android.arch.lifecycle.LiveData;
+import android.arch.persistence.room.Dao;
+import android.arch.persistence.room.Insert;
+import android.arch.persistence.room.OnConflictStrategy;
+import android.arch.persistence.room.Query;
+
+import com.example.android.persistence.db.entity.ProductEntity;
+
+import java.util.List;
+
+@Dao
+public interface ProductDao {
+    @Query("SELECT * FROM products")
+    LiveData<List<ProductEntity>> loadAllProducts();
+
+    @Insert(onConflict = OnConflictStrategy.REPLACE)
+    void insertAll(List<ProductEntity> products);
+
+    @Query("select * from products where id = :productId")
+    LiveData<ProductEntity> loadProduct(int productId);
+
+    @Query("select * from products where id = :productId")
+    ProductEntity loadProductSync(int productId);
+}
diff --git a/samples-flatfoot/PersistenceSample/app/src/main/java/com/example/android/persistence/db/entity/CommentEntity.java b/samples-flatfoot/PersistenceSample/app/src/main/java/com/example/android/persistence/db/entity/CommentEntity.java
new file mode 100644
index 0000000..e3d5702
--- /dev/null
+++ b/samples-flatfoot/PersistenceSample/app/src/main/java/com/example/android/persistence/db/entity/CommentEntity.java
@@ -0,0 +1,86 @@
+/*
+ * Copyright 2017, The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES 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.persistence.db.entity;
+
+import android.arch.persistence.room.Entity;
+import android.arch.persistence.room.ForeignKey;
+import android.arch.persistence.room.Index;
+import android.arch.persistence.room.PrimaryKey;
+import com.example.android.persistence.model.Comment;
+
+import java.util.Date;
+
+@Entity(tableName = "comments", foreignKeys = {
+        @ForeignKey(entity = ProductEntity.class,
+                parentColumns = "id",
+                childColumns = "productId",
+                onDelete = ForeignKey.CASCADE)}, indices = {
+        @Index(value = "productId")
+})
+public class CommentEntity implements Comment {
+    @PrimaryKey(autoGenerate = true)
+    private int id;
+    private int productId;
+    private String text;
+    private Date postedAt;
+
+    @Override
+    public int getId() {
+        return id;
+    }
+
+    public void setId(int id) {
+        this.id = id;
+    }
+
+    @Override
+    public int getProductId() {
+        return productId;
+    }
+
+    public void setProductId(int productId) {
+        this.productId = productId;
+    }
+
+    @Override
+    public String getText() {
+        return text;
+    }
+
+    public void setText(String text) {
+        this.text = text;
+    }
+
+    @Override
+    public Date getPostedAt() {
+        return postedAt;
+    }
+
+    public void setPostedAt(Date postedAt) {
+        this.postedAt = postedAt;
+    }
+
+    public CommentEntity() {
+    }
+
+    public CommentEntity(Comment comment) {
+        id = comment.getId();
+        productId = comment.getProductId();
+        text = comment.getText();
+        postedAt = comment.getPostedAt();
+    }
+}
diff --git a/samples-flatfoot/PersistenceSample/app/src/main/java/com/example/android/persistence/db/entity/ProductEntity.java b/samples-flatfoot/PersistenceSample/app/src/main/java/com/example/android/persistence/db/entity/ProductEntity.java
new file mode 100644
index 0000000..af1b79a
--- /dev/null
+++ b/samples-flatfoot/PersistenceSample/app/src/main/java/com/example/android/persistence/db/entity/ProductEntity.java
@@ -0,0 +1,77 @@
+/*
+ * Copyright 2017, The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES 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.persistence.db.entity;
+
+import android.arch.persistence.room.Entity;
+import android.arch.persistence.room.PrimaryKey;
+
+import com.example.android.persistence.model.Product;
+
+@Entity(tableName = "products")
+public class ProductEntity implements Product {
+    @PrimaryKey
+    private int id;
+    private String name;
+    private String description;
+    private int price;
+
+    @Override
+    public int getId() {
+        return id;
+    }
+
+    public void setId(int id) {
+        this.id = id;
+    }
+
+    @Override
+    public String getName() {
+        return name;
+    }
+
+    public void setName(String name) {
+        this.name = name;
+    }
+
+    @Override
+    public String getDescription() {
+        return description;
+    }
+
+    public void setDescription(String description) {
+        this.description = description;
+    }
+
+    @Override
+    public int getPrice() {
+        return price;
+    }
+
+    public void setPrice(int price) {
+        this.price = price;
+    }
+
+    public ProductEntity() {
+    }
+
+    public ProductEntity(Product product) {
+        this.id = product.getId();
+        this.name = product.getName();
+        this.description = product.getDescription();
+        this.price = product.getPrice();
+    }
+}
diff --git a/samples-flatfoot/PersistenceSample/app/src/main/java/com/example/android/persistence/model/Comment.java b/samples-flatfoot/PersistenceSample/app/src/main/java/com/example/android/persistence/model/Comment.java
new file mode 100644
index 0000000..c3483a4
--- /dev/null
+++ b/samples-flatfoot/PersistenceSample/app/src/main/java/com/example/android/persistence/model/Comment.java
@@ -0,0 +1,26 @@
+/*
+ * Copyright 2017, The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES 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.persistence.model;
+
+import java.util.Date;
+
+public interface Comment {
+    int getId();
+    int getProductId();
+    String getText();
+    Date getPostedAt();
+}
diff --git a/samples-flatfoot/PersistenceSample/app/src/main/java/com/example/android/persistence/model/Product.java b/samples-flatfoot/PersistenceSample/app/src/main/java/com/example/android/persistence/model/Product.java
new file mode 100644
index 0000000..72e4276
--- /dev/null
+++ b/samples-flatfoot/PersistenceSample/app/src/main/java/com/example/android/persistence/model/Product.java
@@ -0,0 +1,24 @@
+/*
+ * Copyright 2017, The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES 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.persistence.model;
+
+public interface Product {
+    int getId();
+    String getName();
+    String getDescription();
+    int getPrice();
+}
diff --git a/samples-flatfoot/PersistenceSample/app/src/main/java/com/example/android/persistence/ui/BindingAdapters.java b/samples-flatfoot/PersistenceSample/app/src/main/java/com/example/android/persistence/ui/BindingAdapters.java
new file mode 100644
index 0000000..0b90335
--- /dev/null
+++ b/samples-flatfoot/PersistenceSample/app/src/main/java/com/example/android/persistence/ui/BindingAdapters.java
@@ -0,0 +1,28 @@
+/*
+ * Copyright 2017, The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES 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.persistence.ui;
+
+import android.databinding.BindingAdapter;
+import android.view.View;
+
+
+public class BindingAdapters {
+    @BindingAdapter("visibleGone")
+    public static void showHide(View view, boolean show) {
+        view.setVisibility(show ? View.VISIBLE : View.GONE);
+    }
+}
\ No newline at end of file
diff --git a/samples-flatfoot/PersistenceSample/app/src/main/java/com/example/android/persistence/ui/CommentAdapter.java b/samples-flatfoot/PersistenceSample/app/src/main/java/com/example/android/persistence/ui/CommentAdapter.java
new file mode 100644
index 0000000..24e0ecb
--- /dev/null
+++ b/samples-flatfoot/PersistenceSample/app/src/main/java/com/example/android/persistence/ui/CommentAdapter.java
@@ -0,0 +1,111 @@
+/*
+ * Copyright 2017, The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES 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.persistence.ui;
+
+import android.databinding.DataBindingUtil;
+import android.support.annotation.Nullable;
+import android.support.v7.util.DiffUtil;
+import android.support.v7.widget.RecyclerView;
+import android.view.LayoutInflater;
+import android.view.ViewGroup;
+
+import com.example.android.persistence.databinding.CommentItemBinding;
+import com.example.android.persistence.model.Comment;
+import com.example.android.persistence.R;
+
+import java.util.List;
+import java.util.Objects;
+
+public class CommentAdapter extends RecyclerView.Adapter<CommentAdapter.CommentViewHolder> {
+
+    private List<? extends Comment> mCommentList;
+
+    @Nullable
+    private final CommentClickCallback mCommentClickCallback;
+
+    public CommentAdapter(@Nullable CommentClickCallback commentClickCallback) {
+        mCommentClickCallback = commentClickCallback;
+    }
+
+    public void setCommentList(final List<? extends Comment> comments) {
+        if (mCommentList == null) {
+            mCommentList = comments;
+            notifyItemRangeInserted(0, comments.size());
+        } else {
+            DiffUtil.DiffResult diffResult = DiffUtil.calculateDiff(new DiffUtil.Callback() {
+                @Override
+                public int getOldListSize() {
+                    return mCommentList.size();
+                }
+
+                @Override
+                public int getNewListSize() {
+                    return comments.size();
+                }
+
+                @Override
+                public boolean areItemsTheSame(int oldItemPosition, int newItemPosition) {
+                    Comment old = mCommentList.get(oldItemPosition);
+                    Comment comment = comments.get(newItemPosition);
+                    return old.getId() == comment.getId();
+                }
+
+                @Override
+                public boolean areContentsTheSame(int oldItemPosition, int newItemPosition) {
+                    Comment old = mCommentList.get(oldItemPosition);
+                    Comment comment = comments.get(newItemPosition);
+                    return old.getId() == comment.getId()
+                            && old.getPostedAt() == comment.getPostedAt()
+                            && old.getProductId() == comment.getProductId()
+                            && Objects.equals(old.getText(), comment.getText());
+                }
+            });
+            mCommentList = comments;
+            diffResult.dispatchUpdatesTo(this);
+        }
+    }
+
+    @Override
+    public CommentViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
+        CommentItemBinding binding = DataBindingUtil
+                .inflate(LayoutInflater.from(parent.getContext()), R.layout.comment_item,
+                        parent, false);
+        binding.setCallback(mCommentClickCallback);
+        return new CommentViewHolder(binding);
+    }
+
+    @Override
+    public void onBindViewHolder(CommentViewHolder holder, int position) {
+        holder.binding.setComment(mCommentList.get(position));
+        holder.binding.executePendingBindings();
+    }
+
+    @Override
+    public int getItemCount() {
+        return mCommentList == null ? 0 : mCommentList.size();
+    }
+
+    static class CommentViewHolder extends RecyclerView.ViewHolder {
+
+        final CommentItemBinding binding;
+
+        CommentViewHolder(CommentItemBinding binding) {
+            super(binding.getRoot());
+            this.binding = binding;
+        }
+    }
+}
diff --git a/samples-flatfoot/PersistenceSample/app/src/main/java/com/example/android/persistence/ui/CommentClickCallback.java b/samples-flatfoot/PersistenceSample/app/src/main/java/com/example/android/persistence/ui/CommentClickCallback.java
new file mode 100644
index 0000000..ced8065
--- /dev/null
+++ b/samples-flatfoot/PersistenceSample/app/src/main/java/com/example/android/persistence/ui/CommentClickCallback.java
@@ -0,0 +1,23 @@
+/*
+ * Copyright 2017, The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES 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.persistence.ui;
+
+import com.example.android.persistence.model.Comment;
+
+public interface CommentClickCallback {
+    void onClick(Comment comment);
+}
diff --git a/samples-flatfoot/PersistenceSample/app/src/main/java/com/example/android/persistence/ui/ProductAdapter.java b/samples-flatfoot/PersistenceSample/app/src/main/java/com/example/android/persistence/ui/ProductAdapter.java
new file mode 100644
index 0000000..e54c1ca
--- /dev/null
+++ b/samples-flatfoot/PersistenceSample/app/src/main/java/com/example/android/persistence/ui/ProductAdapter.java
@@ -0,0 +1,110 @@
+/*
+ * Copyright 2017, The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES 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.persistence.ui;
+
+import android.databinding.DataBindingUtil;
+import android.support.annotation.Nullable;
+import android.support.v7.util.DiffUtil;
+import android.support.v7.widget.RecyclerView;
+import android.view.LayoutInflater;
+import android.view.ViewGroup;
+
+import com.example.android.persistence.databinding.ProductItemBinding;
+import com.example.android.persistence.model.Product;
+import com.example.android.persistence.R;
+
+import java.util.List;
+import java.util.Objects;
+
+public class ProductAdapter extends RecyclerView.Adapter<ProductAdapter.ProductViewHolder> {
+
+    List<? extends Product> mProductList;
+
+    @Nullable
+    private final ProductClickCallback mProductClickCallback;
+
+    public ProductAdapter(@Nullable ProductClickCallback clickCallback) {
+        mProductClickCallback = clickCallback;
+    }
+
+    public void setProductList(final List<? extends Product> productList) {
+        if (mProductList == null) {
+            mProductList = productList;
+            notifyItemRangeInserted(0, productList.size());
+        } else {
+            DiffUtil.DiffResult result = DiffUtil.calculateDiff(new DiffUtil.Callback() {
+                @Override
+                public int getOldListSize() {
+                    return mProductList.size();
+                }
+
+                @Override
+                public int getNewListSize() {
+                    return productList.size();
+                }
+
+                @Override
+                public boolean areItemsTheSame(int oldItemPosition, int newItemPosition) {
+                    return mProductList.get(oldItemPosition).getId() ==
+                            productList.get(newItemPosition).getId();
+                }
+
+                @Override
+                public boolean areContentsTheSame(int oldItemPosition, int newItemPosition) {
+                    Product product = productList.get(newItemPosition);
+                    Product old = productList.get(oldItemPosition);
+                    return product.getId() == old.getId()
+                            && Objects.equals(product.getDescription(), old.getDescription())
+                            && Objects.equals(product.getName(), old.getName())
+                            && product.getPrice() == old.getPrice();
+                }
+            });
+            mProductList = productList;
+            result.dispatchUpdatesTo(this);
+        }
+    }
+
+    @Override
+    public ProductViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
+        ProductItemBinding binding = DataBindingUtil
+                .inflate(LayoutInflater.from(parent.getContext()), R.layout.product_item,
+                        parent, false);
+        binding.setCallback(mProductClickCallback);
+        return new ProductViewHolder(binding);
+    }
+
+    @Override
+    public void onBindViewHolder(ProductViewHolder holder, int position) {
+        holder.binding.setProduct(mProductList.get(position));
+        holder.binding.executePendingBindings();
+    }
+
+    @Override
+    public int getItemCount() {
+        return mProductList == null ? 0 : mProductList.size();
+    }
+
+    static class ProductViewHolder extends RecyclerView.ViewHolder {
+
+        final ProductItemBinding binding;
+
+        public ProductViewHolder(ProductItemBinding binding) {
+            super(binding.getRoot());
+            this.binding = binding;
+        }
+    }
+}
diff --git a/samples-flatfoot/PersistenceSample/app/src/main/java/com/example/android/persistence/ui/ProductClickCallback.java b/samples-flatfoot/PersistenceSample/app/src/main/java/com/example/android/persistence/ui/ProductClickCallback.java
new file mode 100644
index 0000000..b8f4c6b
--- /dev/null
+++ b/samples-flatfoot/PersistenceSample/app/src/main/java/com/example/android/persistence/ui/ProductClickCallback.java
@@ -0,0 +1,23 @@
+/*
+ * Copyright 2017, The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES 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.persistence.ui;
+
+import com.example.android.persistence.model.Product;
+
+public interface ProductClickCallback {
+    void onClick(Product product);
+}
diff --git a/samples-flatfoot/PersistenceSample/app/src/main/java/com/example/android/persistence/viewmodel/ProductListViewModel.java b/samples-flatfoot/PersistenceSample/app/src/main/java/com/example/android/persistence/viewmodel/ProductListViewModel.java
new file mode 100644
index 0000000..8905b14
--- /dev/null
+++ b/samples-flatfoot/PersistenceSample/app/src/main/java/com/example/android/persistence/viewmodel/ProductListViewModel.java
@@ -0,0 +1,70 @@
+/*
+ * Copyright 2017, The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES 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.persistence.viewmodel;
+
+import android.app.Application;
+import android.arch.core.util.Function;
+import android.arch.lifecycle.AndroidViewModel;
+import android.arch.lifecycle.LiveData;
+import android.arch.lifecycle.MutableLiveData;
+import android.arch.lifecycle.Transformations;
+
+import com.example.android.persistence.db.DatabaseCreator;
+import com.example.android.persistence.db.entity.ProductEntity;
+
+import java.util.List;
+
+public class ProductListViewModel extends AndroidViewModel {
+
+    private static final MutableLiveData ABSENT = new MutableLiveData();
+    {
+        //noinspection unchecked
+        ABSENT.setValue(null);
+    }
+
+    private final LiveData<List<ProductEntity>> mObservableProducts;
+
+    public ProductListViewModel(Application application) {
+        super(application);
+
+        final DatabaseCreator databaseCreator = DatabaseCreator.getInstance(this.getApplication());
+
+        LiveData<Boolean> databaseCreated = databaseCreator.isDatabaseCreated();
+        mObservableProducts = Transformations.switchMap(databaseCreated,
+                new Function<Boolean, LiveData<List<ProductEntity>>>() {
+            @Override
+            public LiveData<List<ProductEntity>> apply(Boolean isDbCreated) {
+                if (!Boolean.TRUE.equals(isDbCreated)) { // Not needed here, but watch out for null
+                    //noinspection unchecked
+                    return ABSENT;
+                } else {
+                    //noinspection ConstantConditions
+                    return databaseCreator.getDatabase().productDao().loadAllProducts();
+                }
+            }
+        });
+
+        databaseCreator.createDb(this.getApplication());
+    }
+
+    /**
+     * Expose the LiveData Products query so the UI can observe it.
+     */
+    public LiveData<List<ProductEntity>> getProducts() {
+        return mObservableProducts;
+    }
+}
diff --git a/samples-flatfoot/PersistenceSample/app/src/main/java/com/example/android/persistence/viewmodel/ProductViewModel.java b/samples-flatfoot/PersistenceSample/app/src/main/java/com/example/android/persistence/viewmodel/ProductViewModel.java
new file mode 100644
index 0000000..be524d5
--- /dev/null
+++ b/samples-flatfoot/PersistenceSample/app/src/main/java/com/example/android/persistence/viewmodel/ProductViewModel.java
@@ -0,0 +1,134 @@
+/*
+ * Copyright 2017, The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES 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.persistence.viewmodel;
+
+import android.app.Application;
+import android.arch.core.util.Function;
+import android.arch.lifecycle.AndroidViewModel;
+import android.arch.lifecycle.LiveData;
+import android.arch.lifecycle.MutableLiveData;
+import android.arch.lifecycle.Observer;
+import android.arch.lifecycle.Transformations;
+import android.arch.lifecycle.ViewModel;
+import android.arch.lifecycle.ViewModelProvider;
+import android.databinding.ObservableField;
+import android.support.annotation.NonNull;
+
+import com.example.android.persistence.db.DatabaseCreator;
+import com.example.android.persistence.db.entity.CommentEntity;
+import com.example.android.persistence.db.entity.ProductEntity;
+
+import java.util.List;
+
+public class ProductViewModel extends AndroidViewModel {
+
+    private static final MutableLiveData ABSENT = new MutableLiveData();
+
+    private final LiveData<ProductEntity> mObservableProduct;
+
+    private Observer<ProductEntity> mProductObserver;
+
+    public ObservableField<ProductEntity> product = new ObservableField<>();
+
+    {
+        //noinspection unchecked
+        ABSENT.setValue(null);
+    }
+
+    // Product exposed for data binding
+    //public final ObservableField<ProductEntity> product = new ObservableField<>();
+
+    private final int mProductId;
+
+    private final LiveData<List<CommentEntity>> mObservableComments;
+
+    public ProductViewModel(@NonNull Application application,
+                            final int productId) {
+        super(application);
+        mProductId = productId;
+
+        final DatabaseCreator databaseCreator = DatabaseCreator.getInstance(this.getApplication());
+
+        mObservableComments = Transformations.switchMap(databaseCreator.isDatabaseCreated(), new Function<Boolean, LiveData<List<CommentEntity>>>() {
+            @Override
+            public LiveData<List<CommentEntity>> apply(Boolean isDbCreated) {
+                if (!isDbCreated) {
+                    //noinspection unchecked
+                    return ABSENT;
+                } else {
+                    //noinspection ConstantConditions
+                    return databaseCreator.getDatabase().commentDao().loadComments(mProductId);
+                }
+            }
+        });
+
+        mObservableProduct = Transformations.switchMap(databaseCreator.isDatabaseCreated(), new Function<Boolean, LiveData<ProductEntity>>() {
+            @Override
+            public LiveData<ProductEntity> apply(Boolean isDbCreated) {
+                if (!isDbCreated) {
+                    //noinspection unchecked
+                    return ABSENT;
+                } else {
+                    //noinspection ConstantConditions
+                    return databaseCreator.getDatabase().productDao().loadProduct(mProductId);
+                }
+            }
+        });
+
+        databaseCreator.createDb(this.getApplication());
+
+    }
+    /**
+     * Expose the LiveData Comments query so the UI can observe it.
+     */
+    public LiveData<List<CommentEntity>> getComments() {
+        return mObservableComments;
+    }
+
+    public LiveData<ProductEntity> getObservableProduct() {
+        return mObservableProduct;
+    }
+
+    public void setProduct(ProductEntity product) {
+        this.product.set(product);
+    }
+
+    /**
+     * A creator is used to inject the product ID into the ViewModel
+     * <p>
+     * This creator is to showcase how to inject dependencies into ViewModels. It's not
+     * actually necessary in this case, as the product ID can be passed in a public method.
+     */
+    public static class Factory extends ViewModelProvider.NewInstanceFactory {
+
+        @NonNull
+        private final Application mApplication;
+
+        private final int mProductId;
+
+        public Factory(@NonNull Application application, int productId) {
+            mApplication = application;
+            mProductId = productId;
+        }
+
+        @Override
+        public <T extends ViewModel> T create(Class<T> modelClass) {
+            //noinspection unchecked
+            return (T) new ProductViewModel(mApplication, mProductId);
+        }
+    }
+}
diff --git a/samples-flatfoot/PersistenceSample/app/src/main/res/layout/comment_item.xml b/samples-flatfoot/PersistenceSample/app/src/main/res/layout/comment_item.xml
new file mode 100644
index 0000000..e1bf7f4
--- /dev/null
+++ b/samples-flatfoot/PersistenceSample/app/src/main/res/layout/comment_item.xml
@@ -0,0 +1,46 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  ~ Copyright 2017, The Android Open Source Project
+  ~
+  ~ Licensed under the Apache License, Version 2.0 (the "License");
+  ~ you may not use this file except in compliance with the License.
+  ~ You may obtain a copy of the License at
+  ~
+  ~      http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing, software
+  ~ distributed under the License is distributed on an "AS IS" BASIS,
+  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  ~ See the License for the specific language governing permissions and
+  ~ limitations under the License.
+  -->
+
+<layout xmlns:android="http://schemas.android.com/apk/res/android"
+        xmlns:app="http://schemas.android.com/apk/res-auto">
+    <data>
+        <variable name="comment"
+                  type="com.example.android.persistence.model.Comment"/>
+        <variable name="callback"
+                  type="com.example.android.persistence.ui.CommentClickCallback"/>
+    </data>
+    <android.support.v7.widget.CardView
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        app:cardBackgroundColor="@color/comment_light_background"
+        android:layout_marginStart="@dimen/comment_horizontal_margin"
+        android:layout_marginEnd="@dimen/comment_horizontal_margin"
+
+        android:minHeight="@dimen/comment_minHeight"
+        android:onClick="@{() ->  callback.onClick(comment)}"
+        android:orientation="horizontal"
+        android:padding="8dp"
+        app:cardUseCompatPadding="true">
+        <RelativeLayout android:layout_width="match_parent"
+                        android:layout_height="wrap_content">
+            <TextView android:layout_width="wrap_content"
+                      android:layout_height="wrap_content"
+                      android:layout_margin="@dimen/comment_padding"
+                      android:text="@{comment.text}"/>
+        </RelativeLayout>
+    </android.support.v7.widget.CardView>
+</layout>
diff --git a/samples-flatfoot/PersistenceSample/app/src/main/res/layout/list_fragment.xml b/samples-flatfoot/PersistenceSample/app/src/main/res/layout/list_fragment.xml
new file mode 100644
index 0000000..4f1d382
--- /dev/null
+++ b/samples-flatfoot/PersistenceSample/app/src/main/res/layout/list_fragment.xml
@@ -0,0 +1,51 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  ~ Copyright 2017, The Android Open Source Project
+  ~
+  ~ Licensed under the Apache License, Version 2.0 (the "License");
+  ~ you may not use this file except in compliance with the License.
+  ~ You may obtain a copy of the License at
+  ~
+  ~      http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing, software
+  ~ distributed under the License is distributed on an "AS IS" BASIS,
+  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  ~ See the License for the specific language governing permissions and
+  ~ limitations under the License.
+  -->
+
+<layout xmlns:android="http://schemas.android.com/apk/res/android"
+        xmlns:app="http://schemas.android.com/apk/res-auto">
+
+    <data>
+        <variable
+            name="isLoading"
+            type="boolean" />
+    </data>
+
+    <LinearLayout
+        android:layout_width="match_parent"
+        android:layout_height="match_parent"
+        android:background="@color/cardview_light_background"
+        android:orientation="vertical">
+
+        <TextView
+            android:id="@+id/loading_tv"
+            android:layout_width="match_parent"
+            android:layout_height="match_parent"
+            android:gravity="center_vertical|center_horizontal"
+            android:text="@string/loading_products"
+            android:textAlignment="center"
+            app:visibleGone="@{isLoading}"/>
+
+        <android.support.v7.widget.RecyclerView
+            android:id="@+id/products_list"
+            android:contentDescription="@string/cd_products_list"
+            android:layout_width="match_parent"
+            android:layout_height="match_parent"
+            app:layoutManager="LinearLayoutManager"
+            app:visibleGone="@{!isLoading}"/>
+
+    </LinearLayout>
+</layout>
\ No newline at end of file
diff --git a/samples-flatfoot/PersistenceSample/app/src/main/res/layout/main_activity.xml b/samples-flatfoot/PersistenceSample/app/src/main/res/layout/main_activity.xml
new file mode 100644
index 0000000..29ee166
--- /dev/null
+++ b/samples-flatfoot/PersistenceSample/app/src/main/res/layout/main_activity.xml
@@ -0,0 +1,23 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  ~ Copyright 2017, The Android Open Source Project
+  ~
+  ~ Licensed under the Apache License, Version 2.0 (the "License");
+  ~ you may not use this file except in compliance with the License.
+  ~ You may obtain a copy of the License at
+  ~
+  ~      http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing, software
+  ~ distributed under the License is distributed on an "AS IS" BASIS,
+  ~ WITHOUT 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:id="@+id/fragment_container"
+             android:layout_width="match_parent"
+             android:layout_height="match_parent"
+             android:orientation="vertical">
+</FrameLayout>
\ No newline at end of file
diff --git a/samples-flatfoot/PersistenceSample/app/src/main/res/layout/product_fragment.xml b/samples-flatfoot/PersistenceSample/app/src/main/res/layout/product_fragment.xml
new file mode 100644
index 0000000..f80d69f
--- /dev/null
+++ b/samples-flatfoot/PersistenceSample/app/src/main/res/layout/product_fragment.xml
@@ -0,0 +1,72 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  ~ Copyright 2017, The Android Open Source Project
+  ~
+  ~ Licensed under the Apache License, Version 2.0 (the "License");
+  ~ you may not use this file except in compliance with the License.
+  ~ You may obtain a copy of the License at
+  ~
+  ~      http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing, software
+  ~ distributed under the License is distributed on an "AS IS" BASIS,
+  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  ~ See the License for the specific language governing permissions and
+  ~ limitations under the License.
+  -->
+
+<layout xmlns:android="http://schemas.android.com/apk/res/android"
+        xmlns:app="http://schemas.android.com/apk/res-auto">
+
+    <data>
+
+        <import type="android.view.View"/>
+
+        <variable
+            name="isLoading"
+            type="boolean" />
+
+        <variable
+            name="productViewModel"
+            type="com.example.android.persistence.viewmodel.ProductViewModel"/>
+    </data>
+
+    <LinearLayout
+        android:layout_width="match_parent"
+        android:layout_height="match_parent"
+        android:background="@color/cardview_light_background"
+        android:orientation="vertical">
+
+        <include
+            layout="@layout/product_item"
+            app:product="@{productViewModel.product}"/>
+
+        <FrameLayout
+            android:layout_width="match_parent"
+            android:layout_height="match_parent">
+
+            <TextView
+                android:layout_width="match_parent"
+                android:layout_height="match_parent"
+                android:id="@+id/loading_comments_tv"
+                android:text="@string/loading_comments"
+                app:visibleGone="@{isLoading}"/>
+
+            <FrameLayout
+                android:layout_width="match_parent"
+                android:layout_height="match_parent"
+                android:id="@+id/comments_list_wrapper">
+
+                <android.support.v7.widget.RecyclerView
+                    android:id="@+id/comment_list"
+                    android:contentDescription="@string/cd_comments_list"
+                    android:layout_width="match_parent"
+                    android:layout_height="match_parent"
+                    app:layoutManager="LinearLayoutManager"
+                    app:visibleGone="@{!isLoading}"/>
+            </FrameLayout>
+        </FrameLayout>
+
+
+    </LinearLayout>
+</layout>
\ No newline at end of file
diff --git a/samples-flatfoot/PersistenceSample/app/src/main/res/layout/product_item.xml b/samples-flatfoot/PersistenceSample/app/src/main/res/layout/product_item.xml
new file mode 100644
index 0000000..aa85489
--- /dev/null
+++ b/samples-flatfoot/PersistenceSample/app/src/main/res/layout/product_item.xml
@@ -0,0 +1,65 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  ~ Copyright 2017, The Android Open Source Project
+  ~
+  ~ Licensed under the Apache License, Version 2.0 (the "License");
+  ~ you may not use this file except in compliance with the License.
+  ~ You may obtain a copy of the License at
+  ~
+  ~      http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing, software
+  ~ distributed under the License is distributed on an "AS IS" BASIS,
+  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  ~ See the License for the specific language governing permissions and
+  ~ limitations under the License.
+  -->
+
+<layout xmlns:android="http://schemas.android.com/apk/res/android"
+        xmlns:app="http://schemas.android.com/apk/res-auto">
+    <data>
+        <variable name="product"
+                  type="com.example.android.persistence.model.Product"/>
+        <variable name="callback"
+                  type="com.example.android.persistence.ui.ProductClickCallback"/>
+    </data>
+
+    <android.support.v7.widget.CardView
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        android:minHeight="@dimen/product_item_min_height"
+        android:onClick="@{() ->  callback.onClick(product)}"
+        android:orientation="horizontal"
+        android:layout_marginStart="@dimen/item_horizontal_margin"
+        android:layout_marginEnd="@dimen/item_horizontal_margin"
+        app:cardUseCompatPadding="true">
+
+        <RelativeLayout
+            android:layout_marginStart="@dimen/item_horizontal_margin"
+            android:layout_marginEnd="@dimen/item_horizontal_margin"
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content">
+
+            <TextView
+                android:id="@+id/name"
+                android:layout_width="wrap_content"
+                android:layout_height="wrap_content"
+                android:contentDescription="@string/cd_product_name"
+                android:text="@{product.name}"/>
+
+            <TextView
+                android:layout_width="wrap_content"
+                android:layout_height="wrap_content"
+                android:layout_alignParentEnd="true"
+                android:layout_marginEnd="5dp"
+                android:text="@{@string/product_price(product.price)}"/>
+
+            <TextView
+                android:layout_width="match_parent"
+                android:layout_height="wrap_content"
+                android:layout_below="@id/name"
+                android:text="@{product.description}"/>
+        </RelativeLayout>
+
+    </android.support.v7.widget.CardView>
+</layout>
diff --git a/samples-flatfoot/PersistenceSample/app/src/main/res/mipmap-hdpi/ic_launcher.png b/samples-flatfoot/PersistenceSample/app/src/main/res/mipmap-hdpi/ic_launcher.png
new file mode 100644
index 0000000..e19d44f
--- /dev/null
+++ b/samples-flatfoot/PersistenceSample/app/src/main/res/mipmap-hdpi/ic_launcher.png
Binary files differ
diff --git a/samples-flatfoot/PersistenceSample/app/src/main/res/mipmap-mdpi/ic_launcher.png b/samples-flatfoot/PersistenceSample/app/src/main/res/mipmap-mdpi/ic_launcher.png
new file mode 100644
index 0000000..7876250
--- /dev/null
+++ b/samples-flatfoot/PersistenceSample/app/src/main/res/mipmap-mdpi/ic_launcher.png
Binary files differ
diff --git a/samples-flatfoot/PersistenceSample/app/src/main/res/mipmap-xhdpi/ic_launcher.png b/samples-flatfoot/PersistenceSample/app/src/main/res/mipmap-xhdpi/ic_launcher.png
new file mode 100644
index 0000000..9ae3725
--- /dev/null
+++ b/samples-flatfoot/PersistenceSample/app/src/main/res/mipmap-xhdpi/ic_launcher.png
Binary files differ
diff --git a/samples-flatfoot/PersistenceSample/app/src/main/res/mipmap-xxhdpi/ic_launcher.png b/samples-flatfoot/PersistenceSample/app/src/main/res/mipmap-xxhdpi/ic_launcher.png
new file mode 100644
index 0000000..6a6c3aa
--- /dev/null
+++ b/samples-flatfoot/PersistenceSample/app/src/main/res/mipmap-xxhdpi/ic_launcher.png
Binary files differ
diff --git a/samples-flatfoot/PersistenceSample/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png b/samples-flatfoot/PersistenceSample/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png
new file mode 100644
index 0000000..43ca523
--- /dev/null
+++ b/samples-flatfoot/PersistenceSample/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png
Binary files differ
diff --git a/samples-flatfoot/PersistenceSample/app/src/main/res/values/colors.xml b/samples-flatfoot/PersistenceSample/app/src/main/res/values/colors.xml
new file mode 100644
index 0000000..c481ea4
--- /dev/null
+++ b/samples-flatfoot/PersistenceSample/app/src/main/res/values/colors.xml
@@ -0,0 +1,24 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  ~ Copyright 2017, The Android Open Source Project
+  ~
+  ~ Licensed under the Apache License, Version 2.0 (the "License");
+  ~ you may not use this file except in compliance with the License.
+  ~ You may obtain a copy of the License at
+  ~
+  ~      http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing, software
+  ~ distributed under the License is distributed on an "AS IS" BASIS,
+  ~ WITHOUT 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>
+    <color name="colorPrimary">#3F51B5</color>
+    <color name="colorPrimaryDark">#303F9F</color>
+    <color name="colorAccent">#FF4081</color>
+
+    <color name="comment_light_background">#d6d6d6</color>
+</resources>
diff --git a/samples-flatfoot/PersistenceSample/app/src/main/res/values/dimens.xml b/samples-flatfoot/PersistenceSample/app/src/main/res/values/dimens.xml
new file mode 100644
index 0000000..265e367
--- /dev/null
+++ b/samples-flatfoot/PersistenceSample/app/src/main/res/values/dimens.xml
@@ -0,0 +1,25 @@
+<!--
+  ~ Copyright 2017, The Android Open Source Project
+  ~
+  ~ Licensed under the Apache License, Version 2.0 (the "License");
+  ~ you may not use this file except in compliance with the License.
+  ~ You may obtain a copy of the License at
+  ~
+  ~      http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing, software
+  ~ distributed under the License is distributed on an "AS IS" BASIS,
+  ~ WITHOUT 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="item_horizontal_margin">8dp</dimen>
+    <dimen name="comment_horizontal_margin">16dp</dimen>
+    <dimen name="activity_vertical_margin">16dp</dimen>
+    <dimen name="comment_padding">8dp</dimen>
+    <dimen name="comment_minHeight">64dp</dimen>
+</resources>
diff --git a/samples-flatfoot/PersistenceSample/app/src/main/res/values/product_app.xml b/samples-flatfoot/PersistenceSample/app/src/main/res/values/product_app.xml
new file mode 100644
index 0000000..d1fe819
--- /dev/null
+++ b/samples-flatfoot/PersistenceSample/app/src/main/res/values/product_app.xml
@@ -0,0 +1,21 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  ~ Copyright 2017, The Android Open Source Project
+  ~
+  ~ Licensed under the Apache License, Version 2.0 (the "License");
+  ~ you may not use this file except in compliance with the License.
+  ~ You may obtain a copy of the License at
+  ~
+  ~      http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing, software
+  ~ distributed under the License is distributed on an "AS IS" BASIS,
+  ~ WITHOUT 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="product_price">Price: $%d</string>
+    <dimen name="product_item_min_height">100dp</dimen>
+</resources>
\ No newline at end of file
diff --git a/samples-flatfoot/PersistenceSample/app/src/main/res/values/strings.xml b/samples-flatfoot/PersistenceSample/app/src/main/res/values/strings.xml
new file mode 100644
index 0000000..04a5057
--- /dev/null
+++ b/samples-flatfoot/PersistenceSample/app/src/main/res/values/strings.xml
@@ -0,0 +1,25 @@
+<!--
+  ~ Copyright 2017, The Android Open Source Project
+  ~
+  ~ Licensed under the Apache License, Version 2.0 (the "License");
+  ~ you may not use this file except in compliance with the License.
+  ~ You may obtain a copy of the License at
+  ~
+  ~      http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing, software
+  ~ distributed under the License is distributed on an "AS IS" BASIS,
+  ~ WITHOUT 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">Persistence sample</string>
+    <string name="no_comments">No comments</string>
+    <string name="loading_comments">Loading comments...</string>
+    <string name="loading_products">Loading products...</string>
+    <string name="cd_products_list">Products list</string>
+    <string name="cd_comments_list">Comments list</string>
+    <string name="cd_product_name">Name of the product</string>
+</resources>
diff --git a/samples-flatfoot/PersistenceSample/app/src/main/res/values/styles.xml b/samples-flatfoot/PersistenceSample/app/src/main/res/values/styles.xml
new file mode 100644
index 0000000..7aa7228
--- /dev/null
+++ b/samples-flatfoot/PersistenceSample/app/src/main/res/values/styles.xml
@@ -0,0 +1,27 @@
+<!--
+  ~ Copyright 2017, The Android Open Source Project
+  ~
+  ~ Licensed under the Apache License, Version 2.0 (the "License");
+  ~ you may not use this file except in compliance with the License.
+  ~ You may obtain a copy of the License at
+  ~
+  ~      http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing, software
+  ~ distributed under the License is distributed on an "AS IS" BASIS,
+  ~ WITHOUT 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. -->
+    <style name="AppTheme" parent="Theme.AppCompat.Light.DarkActionBar">
+        <!-- Customize your theme here. -->
+        <item name="colorPrimary">@color/colorPrimary</item>
+        <item name="colorPrimaryDark">@color/colorPrimaryDark</item>
+        <item name="colorAccent">@color/colorAccent</item>
+    </style>
+
+</resources>
diff --git a/samples-flatfoot/PersistenceSample/build.gradle b/samples-flatfoot/PersistenceSample/build.gradle
new file mode 100644
index 0000000..f8c0790
--- /dev/null
+++ b/samples-flatfoot/PersistenceSample/build.gradle
@@ -0,0 +1,50 @@
+/*
+ * Copyright 2017, The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT 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 build file where you can add configuration options common to all sub-projects/modules.
+
+buildscript {
+    repositories {
+        jcenter()
+    }
+    dependencies {
+        classpath 'com.android.tools.build:gradle:2.3.1'
+
+        // NOTE: Do not place your application dependencies here; they belong
+        // in the individual module build.gradle files
+    }
+}
+
+allprojects {
+    repositories {
+        jcenter()
+        maven {
+            url "../../prebuilts0905"
+        }
+    }
+}
+
+task clean(type: Delete) {
+    delete rootProject.buildDir
+}
+
+ext {
+    buildToolsVersion = "25.0.2"
+    supportLibVersion = "25.3.1"
+    runnerVersion = "0.5"
+    rulesVersion = "0.5"
+    espressoVersion = "2.2.2"
+}
\ No newline at end of file
diff --git a/samples-flatfoot/PersistenceSample/gradle.properties b/samples-flatfoot/PersistenceSample/gradle.properties
new file mode 100644
index 0000000..684bee6
--- /dev/null
+++ b/samples-flatfoot/PersistenceSample/gradle.properties
@@ -0,0 +1,33 @@
+#
+# Copyright 2017, The Android Open Source Project
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#      http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+
+# Project-wide Gradle settings.
+
+# IDE (e.g. Android Studio) users:
+# Gradle settings configured through the IDE *will override*
+# any settings specified in this file.
+
+# For more details on how to configure your build environment visit
+# http://www.gradle.org/docs/current/userguide/build_environment.html
+
+# Specifies the JVM arguments used for the daemon process.
+# The setting is particularly useful for tweaking memory settings.
+org.gradle.jvmargs=-Xmx1536m
+
+# When configured, Gradle will run in incubating parallel mode.
+# This option should only be used with decoupled projects. More details, visit
+# http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects
+# org.gradle.parallel=true
diff --git a/samples-flatfoot/PersistenceSample/gradle/wrapper/gradle-wrapper.jar b/samples-flatfoot/PersistenceSample/gradle/wrapper/gradle-wrapper.jar
new file mode 100644
index 0000000..13372ae
--- /dev/null
+++ b/samples-flatfoot/PersistenceSample/gradle/wrapper/gradle-wrapper.jar
Binary files differ
diff --git a/samples-flatfoot/PersistenceSample/gradle/wrapper/gradle-wrapper.properties b/samples-flatfoot/PersistenceSample/gradle/wrapper/gradle-wrapper.properties
new file mode 100644
index 0000000..37b4e8c
--- /dev/null
+++ b/samples-flatfoot/PersistenceSample/gradle/wrapper/gradle-wrapper.properties
@@ -0,0 +1,22 @@
+#
+# Copyright 2017, The Android Open Source Project
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#      http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+
+#Mon Apr 24 18:19:01 CEST 2017
+distributionBase=GRADLE_USER_HOME
+distributionPath=wrapper/dists
+zipStoreBase=GRADLE_USER_HOME
+zipStorePath=wrapper/dists
+distributionUrl=https\://services.gradle.org/distributions/gradle-3.4.1-all.zip
diff --git a/samples-flatfoot/PersistenceSample/gradlew b/samples-flatfoot/PersistenceSample/gradlew
new file mode 100755
index 0000000..9d82f78
--- /dev/null
+++ b/samples-flatfoot/PersistenceSample/gradlew
@@ -0,0 +1,160 @@
+#!/usr/bin/env bash
+
+##############################################################################
+##
+##  Gradle start up script for UN*X
+##
+##############################################################################
+
+# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
+DEFAULT_JVM_OPTS=""
+
+APP_NAME="Gradle"
+APP_BASE_NAME=`basename "$0"`
+
+# Use the maximum available, or set MAX_FD != -1 to use that value.
+MAX_FD="maximum"
+
+warn ( ) {
+    echo "$*"
+}
+
+die ( ) {
+    echo
+    echo "$*"
+    echo
+    exit 1
+}
+
+# OS specific support (must be 'true' or 'false').
+cygwin=false
+msys=false
+darwin=false
+case "`uname`" in
+  CYGWIN* )
+    cygwin=true
+    ;;
+  Darwin* )
+    darwin=true
+    ;;
+  MINGW* )
+    msys=true
+    ;;
+esac
+
+# Attempt to set APP_HOME
+# Resolve links: $0 may be a link
+PRG="$0"
+# Need this for relative symlinks.
+while [ -h "$PRG" ] ; do
+    ls=`ls -ld "$PRG"`
+    link=`expr "$ls" : '.*-> \(.*\)$'`
+    if expr "$link" : '/.*' > /dev/null; then
+        PRG="$link"
+    else
+        PRG=`dirname "$PRG"`"/$link"
+    fi
+done
+SAVED="`pwd`"
+cd "`dirname \"$PRG\"`/" >/dev/null
+APP_HOME="`pwd -P`"
+cd "$SAVED" >/dev/null
+
+CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
+
+# Determine the Java command to use to start the JVM.
+if [ -n "$JAVA_HOME" ] ; then
+    if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
+        # IBM's JDK on AIX uses strange locations for the executables
+        JAVACMD="$JAVA_HOME/jre/sh/java"
+    else
+        JAVACMD="$JAVA_HOME/bin/java"
+    fi
+    if [ ! -x "$JAVACMD" ] ; then
+        die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
+
+Please set the JAVA_HOME variable in your environment to match the
+location of your Java installation."
+    fi
+else
+    JAVACMD="java"
+    which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
+
+Please set the JAVA_HOME variable in your environment to match the
+location of your Java installation."
+fi
+
+# Increase the maximum file descriptors if we can.
+if [ "$cygwin" = "false" -a "$darwin" = "false" ] ; then
+    MAX_FD_LIMIT=`ulimit -H -n`
+    if [ $? -eq 0 ] ; then
+        if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then
+            MAX_FD="$MAX_FD_LIMIT"
+        fi
+        ulimit -n $MAX_FD
+        if [ $? -ne 0 ] ; then
+            warn "Could not set maximum file descriptor limit: $MAX_FD"
+        fi
+    else
+        warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT"
+    fi
+fi
+
+# For Darwin, add options to specify how the application appears in the dock
+if $darwin; then
+    GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\""
+fi
+
+# For Cygwin, switch paths to Windows format before running java
+if $cygwin ; then
+    APP_HOME=`cygpath --path --mixed "$APP_HOME"`
+    CLASSPATH=`cygpath --path --mixed "$CLASSPATH"`
+    JAVACMD=`cygpath --unix "$JAVACMD"`
+
+    # We build the pattern for arguments to be converted via cygpath
+    ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null`
+    SEP=""
+    for dir in $ROOTDIRSRAW ; do
+        ROOTDIRS="$ROOTDIRS$SEP$dir"
+        SEP="|"
+    done
+    OURCYGPATTERN="(^($ROOTDIRS))"
+    # Add a user-defined pattern to the cygpath arguments
+    if [ "$GRADLE_CYGPATTERN" != "" ] ; then
+        OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)"
+    fi
+    # Now convert the arguments - kludge to limit ourselves to /bin/sh
+    i=0
+    for arg in "$@" ; do
+        CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -`
+        CHECK2=`echo "$arg"|egrep -c "^-"`                                 ### Determine if an option
+
+        if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then                    ### Added a condition
+            eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"`
+        else
+            eval `echo args$i`="\"$arg\""
+        fi
+        i=$((i+1))
+    done
+    case $i in
+        (0) set -- ;;
+        (1) set -- "$args0" ;;
+        (2) set -- "$args0" "$args1" ;;
+        (3) set -- "$args0" "$args1" "$args2" ;;
+        (4) set -- "$args0" "$args1" "$args2" "$args3" ;;
+        (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;;
+        (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;;
+        (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;;
+        (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;;
+        (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;;
+    esac
+fi
+
+# Split up the JVM_OPTS And GRADLE_OPTS values into an array, following the shell quoting and substitution rules
+function splitJvmOpts() {
+    JVM_OPTS=("$@")
+}
+eval splitJvmOpts $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS
+JVM_OPTS[${#JVM_OPTS[*]}]="-Dorg.gradle.appname=$APP_BASE_NAME"
+
+exec "$JAVACMD" "${JVM_OPTS[@]}" -classpath "$CLASSPATH" org.gradle.wrapper.GradleWrapperMain "$@"
diff --git a/samples-flatfoot/PersistenceSample/gradlew.bat b/samples-flatfoot/PersistenceSample/gradlew.bat
new file mode 100644
index 0000000..aec9973
--- /dev/null
+++ b/samples-flatfoot/PersistenceSample/gradlew.bat
@@ -0,0 +1,90 @@
+@if "%DEBUG%" == "" @echo off

+@rem ##########################################################################

+@rem

+@rem  Gradle startup script for Windows

+@rem

+@rem ##########################################################################

+

+@rem Set local scope for the variables with windows NT shell

+if "%OS%"=="Windows_NT" setlocal

+

+@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.

+set DEFAULT_JVM_OPTS=

+

+set DIRNAME=%~dp0

+if "%DIRNAME%" == "" set DIRNAME=.

+set APP_BASE_NAME=%~n0

+set APP_HOME=%DIRNAME%

+

+@rem Find java.exe

+if defined JAVA_HOME goto findJavaFromJavaHome

+

+set JAVA_EXE=java.exe

+%JAVA_EXE% -version >NUL 2>&1

+if "%ERRORLEVEL%" == "0" goto init

+

+echo.

+echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.

+echo.

+echo Please set the JAVA_HOME variable in your environment to match the

+echo location of your Java installation.

+

+goto fail

+

+:findJavaFromJavaHome

+set JAVA_HOME=%JAVA_HOME:"=%

+set JAVA_EXE=%JAVA_HOME%/bin/java.exe

+

+if exist "%JAVA_EXE%" goto init

+

+echo.

+echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%

+echo.

+echo Please set the JAVA_HOME variable in your environment to match the

+echo location of your Java installation.

+

+goto fail

+

+:init

+@rem Get command-line arguments, handling Windowz variants

+

+if not "%OS%" == "Windows_NT" goto win9xME_args

+if "%@eval[2+2]" == "4" goto 4NT_args

+

+:win9xME_args

+@rem Slurp the command line arguments.

+set CMD_LINE_ARGS=

+set _SKIP=2

+

+:win9xME_args_slurp

+if "x%~1" == "x" goto execute

+

+set CMD_LINE_ARGS=%*

+goto execute

+

+:4NT_args

+@rem Get arguments from the 4NT Shell from JP Software

+set CMD_LINE_ARGS=%$

+

+:execute

+@rem Setup the command line

+

+set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar

+

+@rem Execute Gradle

+"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS%

+

+:end

+@rem End local scope for the variables with windows NT shell

+if "%ERRORLEVEL%"=="0" goto mainEnd

+

+:fail

+rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of

+rem the _cmd.exe /c_ return code!

+if  not "" == "%GRADLE_EXIT_CONSOLE%" exit 1

+exit /b 1

+

+:mainEnd

+if "%OS%"=="Windows_NT" endlocal

+

+:omega

diff --git a/samples-flatfoot/PersistenceSample/settings.gradle b/samples-flatfoot/PersistenceSample/settings.gradle
new file mode 100644
index 0000000..a266f7d
--- /dev/null
+++ b/samples-flatfoot/PersistenceSample/settings.gradle
@@ -0,0 +1,17 @@
+/*
+ * Copyright 2017, The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+include ':app'
diff --git a/samples-flatfoot/codelabs/CONTRIBUTING.md b/samples-flatfoot/codelabs/CONTRIBUTING.md
new file mode 100644
index 0000000..7b86f95
--- /dev/null
+++ b/samples-flatfoot/codelabs/CONTRIBUTING.md
@@ -0,0 +1,35 @@
+# How to become a contributor and submit your own code
+
+## Contributor License Agreements
+
+We'd love to accept your sample apps and patches! Before we can take them, we
+have to jump a couple of legal hurdles.
+
+Please fill out either the individual or corporate Contributor License Agreement (CLA).
+
+  * If you are an individual writing original source code and you're sure you
+    own the intellectual property, then you'll need to sign an [individual CLA]
+    (https://cla.developers.google.com).
+  * If you work for a company that wants to allow you to contribute your work,
+    then you'll need to sign a [corporate CLA]
+    (https://cla.developers.google.com).
+  * Please make sure you sign both, Android and Google CLA
+
+Follow either of the two links above to access the appropriate CLA and
+instructions for how to sign and return it. Once we receive it, we'll be able to
+accept your pull requests.
+
+## Contributing A Patch
+
+1. Submit an issue describing your proposed change to the repo in question.
+1. The repo owner will respond to your issue promptly.
+1. If your proposed change is accepted, and you haven't already done so, sign a
+   Contributor License Agreement (see details above).
+1. Fork the desired repo, develop and test your code changes.
+1. Ensure that your code adheres to the existing style in the sample to which
+   you are contributing. Refer to the
+   [Android Code Style Guide]
+   (https://source.android.com/source/code-style.html) for the
+   recommended coding standards for this organization.
+1. Ensure that your code has an appropriate set of unit tests which all pass.
+1. Submit a pull request.
diff --git a/samples-flatfoot/codelabs/LICENSE b/samples-flatfoot/codelabs/LICENSE
new file mode 100644
index 0000000..1af981f
--- /dev/null
+++ b/samples-flatfoot/codelabs/LICENSE
@@ -0,0 +1,201 @@
+                                 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
+
+   APPENDIX: How to apply the Apache License to your work.
+
+      To apply the Apache License to your work, attach the following
+      boilerplate notice, with the fields enclosed by brackets "[]"
+      replaced with your own identifying information. (Don't include
+      the brackets!)  The text should be enclosed in the appropriate
+      comment syntax for the file format. We also recommend that a
+      file or class name and description of purpose be included on the
+      same "printed page" as the copyright notice for easier
+      identification within third-party archives.
+
+   Copyright 2014 The Android Open Source Project
+
+   Licensed under the Apache License, Version 2.0 (the "License");
+   you may not use this file except in compliance with the License.
+   You may obtain a copy of the License at
+
+       http://www.apache.org/licenses/LICENSE-2.0
+
+   Unless required by applicable law or agreed to in writing, software
+   distributed under the License is distributed on an "AS IS" BASIS,
+   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+   See the License for the specific language governing permissions and
+   limitations under the License.
diff --git a/samples-flatfoot/codelabs/lifecycle/.gitignore b/samples-flatfoot/codelabs/lifecycle/.gitignore
new file mode 100644
index 0000000..c33eb5b
--- /dev/null
+++ b/samples-flatfoot/codelabs/lifecycle/.gitignore
@@ -0,0 +1,9 @@
+*.iml
+.gradle
+/local.properties
+/.idea/workspace.xml
+/.idea/libraries
+.DS_Store
+build/
+/captures
+.externalNativeBuild
diff --git a/samples-flatfoot/codelabs/lifecycle/.idea/runConfigurations/Step_1.xml b/samples-flatfoot/codelabs/lifecycle/.idea/runConfigurations/Step_1.xml
new file mode 100644
index 0000000..0dfbdcb
--- /dev/null
+++ b/samples-flatfoot/codelabs/lifecycle/.idea/runConfigurations/Step_1.xml
@@ -0,0 +1,51 @@
+<component name="ProjectRunConfigurationManager">
+  <configuration default="false" name="Step 1" type="AndroidRunConfigurationType" factoryName="Android App" activateToolWindowBeforeRun="false">
+    <module name="app" />
+    <option name="DEPLOY" value="true" />
+    <option name="ARTIFACT_NAME" value="" />
+    <option name="PM_INSTALL_OPTIONS" value="" />
+    <option name="ACTIVITY_EXTRA_FLAGS" value="" />
+    <option name="MODE" value="specific_activity" />
+    <option name="PREFERRED_AVD" value="" />
+    <option name="CLEAR_LOGCAT" value="false" />
+    <option name="SHOW_LOGCAT_AUTOMATICALLY" value="false" />
+    <option name="SKIP_NOOP_APK_INSTALLATIONS" value="true" />
+    <option name="FORCE_STOP_RUNNING_APP" value="true" />
+    <option name="TARGET_SELECTION_MODE" value="SHOW_DIALOG" />
+    <option name="USE_LAST_SELECTED_DEVICE" value="false" />
+    <option name="PREFERRED_AVD" value="" />
+    <option name="SELECTED_CLOUD_MATRIX_CONFIGURATION_ID" value="-1" />
+    <option name="SELECTED_CLOUD_MATRIX_PROJECT_ID" value="" />
+    <option name="DEBUGGER_TYPE" value="Auto" />
+    <Auto>
+      <option name="USE_JAVA_AWARE_DEBUGGER" value="false" />
+      <option name="SHOW_STATIC_VARS" value="true" />
+      <option name="WORKING_DIR" value="" />
+      <option name="TARGET_LOGGING_CHANNELS" value="lldb process:gdb-remote packets" />
+      <option name="SHOW_OPTIMIZED_WARNING" value="true" />
+    </Auto>
+    <Hybrid>
+      <option name="USE_JAVA_AWARE_DEBUGGER" value="false" />
+      <option name="SHOW_STATIC_VARS" value="true" />
+      <option name="WORKING_DIR" value="" />
+      <option name="TARGET_LOGGING_CHANNELS" value="lldb process:gdb-remote packets" />
+      <option name="SHOW_OPTIMIZED_WARNING" value="true" />
+    </Hybrid>
+    <Java />
+    <Native>
+      <option name="USE_JAVA_AWARE_DEBUGGER" value="false" />
+      <option name="SHOW_STATIC_VARS" value="true" />
+      <option name="WORKING_DIR" value="" />
+      <option name="TARGET_LOGGING_CHANNELS" value="lldb process:gdb-remote packets" />
+      <option name="SHOW_OPTIMIZED_WARNING" value="true" />
+    </Native>
+    <Profilers>
+      <option name="ENABLE_ADVANCED_PROFILING" value="true" />
+      <option name="SUPPORT_LIB_ENABLED" value="true" />
+      <option name="INSTRUMENTATION_ENABLED" value="true" />
+    </Profilers>
+    <option name="DEEP_LINK" value="" />
+    <option name="ACTIVITY_CLASS" value="com.example.android.lifecycles.step1.ChronoActivity1" />
+    <method />
+  </configuration>
+</component>
\ No newline at end of file
diff --git a/samples-flatfoot/codelabs/lifecycle/.idea/runConfigurations/Step_2___ViewModel.xml b/samples-flatfoot/codelabs/lifecycle/.idea/runConfigurations/Step_2___ViewModel.xml
new file mode 100644
index 0000000..7d2f95d
--- /dev/null
+++ b/samples-flatfoot/codelabs/lifecycle/.idea/runConfigurations/Step_2___ViewModel.xml
@@ -0,0 +1,51 @@
+<component name="ProjectRunConfigurationManager">
+  <configuration default="false" name="Step 2 - ViewModel" type="AndroidRunConfigurationType" factoryName="Android App" activateToolWindowBeforeRun="false">
+    <module name="app" />
+    <option name="DEPLOY" value="true" />
+    <option name="ARTIFACT_NAME" value="" />
+    <option name="PM_INSTALL_OPTIONS" value="" />
+    <option name="ACTIVITY_EXTRA_FLAGS" value="" />
+    <option name="MODE" value="specific_activity" />
+    <option name="PREFERRED_AVD" value="" />
+    <option name="CLEAR_LOGCAT" value="false" />
+    <option name="SHOW_LOGCAT_AUTOMATICALLY" value="false" />
+    <option name="SKIP_NOOP_APK_INSTALLATIONS" value="true" />
+    <option name="FORCE_STOP_RUNNING_APP" value="true" />
+    <option name="TARGET_SELECTION_MODE" value="SHOW_DIALOG" />
+    <option name="USE_LAST_SELECTED_DEVICE" value="true" />
+    <option name="PREFERRED_AVD" value="" />
+    <option name="SELECTED_CLOUD_MATRIX_CONFIGURATION_ID" value="-1" />
+    <option name="SELECTED_CLOUD_MATRIX_PROJECT_ID" value="" />
+    <option name="DEBUGGER_TYPE" value="Auto" />
+    <Auto>
+      <option name="USE_JAVA_AWARE_DEBUGGER" value="false" />
+      <option name="SHOW_STATIC_VARS" value="true" />
+      <option name="WORKING_DIR" value="" />
+      <option name="TARGET_LOGGING_CHANNELS" value="lldb process:gdb-remote packets" />
+      <option name="SHOW_OPTIMIZED_WARNING" value="true" />
+    </Auto>
+    <Hybrid>
+      <option name="USE_JAVA_AWARE_DEBUGGER" value="false" />
+      <option name="SHOW_STATIC_VARS" value="true" />
+      <option name="WORKING_DIR" value="" />
+      <option name="TARGET_LOGGING_CHANNELS" value="lldb process:gdb-remote packets" />
+      <option name="SHOW_OPTIMIZED_WARNING" value="true" />
+    </Hybrid>
+    <Java />
+    <Native>
+      <option name="USE_JAVA_AWARE_DEBUGGER" value="false" />
+      <option name="SHOW_STATIC_VARS" value="true" />
+      <option name="WORKING_DIR" value="" />
+      <option name="TARGET_LOGGING_CHANNELS" value="lldb process:gdb-remote packets" />
+      <option name="SHOW_OPTIMIZED_WARNING" value="true" />
+    </Native>
+    <Profilers>
+      <option name="ENABLE_ADVANCED_PROFILING" value="true" />
+      <option name="SUPPORT_LIB_ENABLED" value="true" />
+      <option name="INSTRUMENTATION_ENABLED" value="true" />
+    </Profilers>
+    <option name="DEEP_LINK" value="" />
+    <option name="ACTIVITY_CLASS" value="com.example.android.lifecycles.step2.ChronoActivity2" />
+    <method />
+  </configuration>
+</component>
\ No newline at end of file
diff --git a/samples-flatfoot/codelabs/lifecycle/.idea/runConfigurations/Step_3___LiveData.xml b/samples-flatfoot/codelabs/lifecycle/.idea/runConfigurations/Step_3___LiveData.xml
new file mode 100644
index 0000000..c924446
--- /dev/null
+++ b/samples-flatfoot/codelabs/lifecycle/.idea/runConfigurations/Step_3___LiveData.xml
@@ -0,0 +1,51 @@
+<component name="ProjectRunConfigurationManager">
+  <configuration default="false" name="Step 3 - LiveData" type="AndroidRunConfigurationType" factoryName="Android App" activateToolWindowBeforeRun="false">
+    <module name="app" />
+    <option name="DEPLOY" value="true" />
+    <option name="ARTIFACT_NAME" value="" />
+    <option name="PM_INSTALL_OPTIONS" value="" />
+    <option name="ACTIVITY_EXTRA_FLAGS" value="" />
+    <option name="MODE" value="specific_activity" />
+    <option name="PREFERRED_AVD" value="" />
+    <option name="CLEAR_LOGCAT" value="false" />
+    <option name="SHOW_LOGCAT_AUTOMATICALLY" value="false" />
+    <option name="SKIP_NOOP_APK_INSTALLATIONS" value="true" />
+    <option name="FORCE_STOP_RUNNING_APP" value="true" />
+    <option name="TARGET_SELECTION_MODE" value="SHOW_DIALOG" />
+    <option name="USE_LAST_SELECTED_DEVICE" value="false" />
+    <option name="PREFERRED_AVD" value="" />
+    <option name="SELECTED_CLOUD_MATRIX_CONFIGURATION_ID" value="-1" />
+    <option name="SELECTED_CLOUD_MATRIX_PROJECT_ID" value="" />
+    <option name="DEBUGGER_TYPE" value="Auto" />
+    <Auto>
+      <option name="USE_JAVA_AWARE_DEBUGGER" value="false" />
+      <option name="SHOW_STATIC_VARS" value="true" />
+      <option name="WORKING_DIR" value="" />
+      <option name="TARGET_LOGGING_CHANNELS" value="lldb process:gdb-remote packets" />
+      <option name="SHOW_OPTIMIZED_WARNING" value="true" />
+    </Auto>
+    <Hybrid>
+      <option name="USE_JAVA_AWARE_DEBUGGER" value="false" />
+      <option name="SHOW_STATIC_VARS" value="true" />
+      <option name="WORKING_DIR" value="" />
+      <option name="TARGET_LOGGING_CHANNELS" value="lldb process:gdb-remote packets" />
+      <option name="SHOW_OPTIMIZED_WARNING" value="true" />
+    </Hybrid>
+    <Java />
+    <Native>
+      <option name="USE_JAVA_AWARE_DEBUGGER" value="false" />
+      <option name="SHOW_STATIC_VARS" value="true" />
+      <option name="WORKING_DIR" value="" />
+      <option name="TARGET_LOGGING_CHANNELS" value="lldb process:gdb-remote packets" />
+      <option name="SHOW_OPTIMIZED_WARNING" value="true" />
+    </Native>
+    <Profilers>
+      <option name="ENABLE_ADVANCED_PROFILING" value="true" />
+      <option name="SUPPORT_LIB_ENABLED" value="true" />
+      <option name="INSTRUMENTATION_ENABLED" value="true" />
+    </Profilers>
+    <option name="DEEP_LINK" value="" />
+    <option name="ACTIVITY_CLASS" value="com.example.android.lifecycles.step3.ChronoActivity3" />
+    <method />
+  </configuration>
+</component>
\ No newline at end of file
diff --git a/samples-flatfoot/codelabs/lifecycle/.idea/runConfigurations/Step_3___LiveData_solution.xml b/samples-flatfoot/codelabs/lifecycle/.idea/runConfigurations/Step_3___LiveData_solution.xml
new file mode 100644
index 0000000..546a8ad
--- /dev/null
+++ b/samples-flatfoot/codelabs/lifecycle/.idea/runConfigurations/Step_3___LiveData_solution.xml
@@ -0,0 +1,51 @@
+<component name="ProjectRunConfigurationManager">
+  <configuration default="false" name="Step 3 - LiveData solution" type="AndroidRunConfigurationType" factoryName="Android App" activateToolWindowBeforeRun="false">
+    <module name="app" />
+    <option name="DEPLOY" value="true" />
+    <option name="ARTIFACT_NAME" value="" />
+    <option name="PM_INSTALL_OPTIONS" value="" />
+    <option name="ACTIVITY_EXTRA_FLAGS" value="" />
+    <option name="MODE" value="specific_activity" />
+    <option name="PREFERRED_AVD" value="" />
+    <option name="CLEAR_LOGCAT" value="false" />
+    <option name="SHOW_LOGCAT_AUTOMATICALLY" value="false" />
+    <option name="SKIP_NOOP_APK_INSTALLATIONS" value="true" />
+    <option name="FORCE_STOP_RUNNING_APP" value="true" />
+    <option name="TARGET_SELECTION_MODE" value="SHOW_DIALOG" />
+    <option name="USE_LAST_SELECTED_DEVICE" value="true" />
+    <option name="PREFERRED_AVD" value="" />
+    <option name="SELECTED_CLOUD_MATRIX_CONFIGURATION_ID" value="-1" />
+    <option name="SELECTED_CLOUD_MATRIX_PROJECT_ID" value="" />
+    <option name="DEBUGGER_TYPE" value="Auto" />
+    <Auto>
+      <option name="USE_JAVA_AWARE_DEBUGGER" value="false" />
+      <option name="SHOW_STATIC_VARS" value="true" />
+      <option name="WORKING_DIR" value="" />
+      <option name="TARGET_LOGGING_CHANNELS" value="lldb process:gdb-remote packets" />
+      <option name="SHOW_OPTIMIZED_WARNING" value="true" />
+    </Auto>
+    <Hybrid>
+      <option name="USE_JAVA_AWARE_DEBUGGER" value="false" />
+      <option name="SHOW_STATIC_VARS" value="true" />
+      <option name="WORKING_DIR" value="" />
+      <option name="TARGET_LOGGING_CHANNELS" value="lldb process:gdb-remote packets" />
+      <option name="SHOW_OPTIMIZED_WARNING" value="true" />
+    </Hybrid>
+    <Java />
+    <Native>
+      <option name="USE_JAVA_AWARE_DEBUGGER" value="false" />
+      <option name="SHOW_STATIC_VARS" value="true" />
+      <option name="WORKING_DIR" value="" />
+      <option name="TARGET_LOGGING_CHANNELS" value="lldb process:gdb-remote packets" />
+      <option name="SHOW_OPTIMIZED_WARNING" value="true" />
+    </Native>
+    <Profilers>
+      <option name="ENABLE_ADVANCED_PROFILING" value="true" />
+      <option name="SUPPORT_LIB_ENABLED" value="true" />
+      <option name="INSTRUMENTATION_ENABLED" value="true" />
+    </Profilers>
+    <option name="DEEP_LINK" value="" />
+    <option name="ACTIVITY_CLASS" value="com.example.android.lifecycles.step3_solution.ChronoActivity3" />
+    <method />
+  </configuration>
+</component>
\ No newline at end of file
diff --git a/samples-flatfoot/codelabs/lifecycle/.idea/runConfigurations/Step_4___Lifecycle_provider.xml b/samples-flatfoot/codelabs/lifecycle/.idea/runConfigurations/Step_4___Lifecycle_provider.xml
new file mode 100644
index 0000000..c23cba6
--- /dev/null
+++ b/samples-flatfoot/codelabs/lifecycle/.idea/runConfigurations/Step_4___Lifecycle_provider.xml
@@ -0,0 +1,51 @@
+<component name="ProjectRunConfigurationManager">
+  <configuration default="false" name="Step 4 - Lifecycle provider" type="AndroidRunConfigurationType" factoryName="Android App" activateToolWindowBeforeRun="false">
+    <module name="app" />
+    <option name="DEPLOY" value="true" />
+    <option name="ARTIFACT_NAME" value="" />
+    <option name="PM_INSTALL_OPTIONS" value="" />
+    <option name="ACTIVITY_EXTRA_FLAGS" value="" />
+    <option name="MODE" value="specific_activity" />
+    <option name="PREFERRED_AVD" value="" />
+    <option name="CLEAR_LOGCAT" value="false" />
+    <option name="SHOW_LOGCAT_AUTOMATICALLY" value="false" />
+    <option name="SKIP_NOOP_APK_INSTALLATIONS" value="true" />
+    <option name="FORCE_STOP_RUNNING_APP" value="true" />
+    <option name="TARGET_SELECTION_MODE" value="SHOW_DIALOG" />
+    <option name="USE_LAST_SELECTED_DEVICE" value="false" />
+    <option name="PREFERRED_AVD" value="" />
+    <option name="SELECTED_CLOUD_MATRIX_CONFIGURATION_ID" value="-1" />
+    <option name="SELECTED_CLOUD_MATRIX_PROJECT_ID" value="" />
+    <option name="DEBUGGER_TYPE" value="Auto" />
+    <Auto>
+      <option name="USE_JAVA_AWARE_DEBUGGER" value="false" />
+      <option name="SHOW_STATIC_VARS" value="true" />
+      <option name="WORKING_DIR" value="" />
+      <option name="TARGET_LOGGING_CHANNELS" value="lldb process:gdb-remote packets" />
+      <option name="SHOW_OPTIMIZED_WARNING" value="true" />
+    </Auto>
+    <Hybrid>
+      <option name="USE_JAVA_AWARE_DEBUGGER" value="false" />
+      <option name="SHOW_STATIC_VARS" value="true" />
+      <option name="WORKING_DIR" value="" />
+      <option name="TARGET_LOGGING_CHANNELS" value="lldb process:gdb-remote packets" />
+      <option name="SHOW_OPTIMIZED_WARNING" value="true" />
+    </Hybrid>
+    <Java />
+    <Native>
+      <option name="USE_JAVA_AWARE_DEBUGGER" value="false" />
+      <option name="SHOW_STATIC_VARS" value="true" />
+      <option name="WORKING_DIR" value="" />
+      <option name="TARGET_LOGGING_CHANNELS" value="lldb process:gdb-remote packets" />
+      <option name="SHOW_OPTIMIZED_WARNING" value="true" />
+    </Native>
+    <Profilers>
+      <option name="ENABLE_ADVANCED_PROFILING" value="true" />
+      <option name="SUPPORT_LIB_ENABLED" value="true" />
+      <option name="INSTRUMENTATION_ENABLED" value="true" />
+    </Profilers>
+    <option name="DEEP_LINK" value="" />
+    <option name="ACTIVITY_CLASS" value="com.example.android.lifecycles.step4.LocationActivity" />
+    <method />
+  </configuration>
+</component>
\ No newline at end of file
diff --git a/samples-flatfoot/codelabs/lifecycle/.idea/runConfigurations/Step_4___Lifecycle_provider_solution.xml b/samples-flatfoot/codelabs/lifecycle/.idea/runConfigurations/Step_4___Lifecycle_provider_solution.xml
new file mode 100644
index 0000000..01c21e4
--- /dev/null
+++ b/samples-flatfoot/codelabs/lifecycle/.idea/runConfigurations/Step_4___Lifecycle_provider_solution.xml
@@ -0,0 +1,51 @@
+<component name="ProjectRunConfigurationManager">
+  <configuration default="false" name="Step 4 - Lifecycle provider solution" type="AndroidRunConfigurationType" factoryName="Android App" activateToolWindowBeforeRun="false">
+    <module name="app" />
+    <option name="DEPLOY" value="true" />
+    <option name="ARTIFACT_NAME" value="" />
+    <option name="PM_INSTALL_OPTIONS" value="" />
+    <option name="ACTIVITY_EXTRA_FLAGS" value="" />
+    <option name="MODE" value="specific_activity" />
+    <option name="PREFERRED_AVD" value="" />
+    <option name="CLEAR_LOGCAT" value="false" />
+    <option name="SHOW_LOGCAT_AUTOMATICALLY" value="false" />
+    <option name="SKIP_NOOP_APK_INSTALLATIONS" value="true" />
+    <option name="FORCE_STOP_RUNNING_APP" value="true" />
+    <option name="TARGET_SELECTION_MODE" value="SHOW_DIALOG" />
+    <option name="USE_LAST_SELECTED_DEVICE" value="false" />
+    <option name="PREFERRED_AVD" value="" />
+    <option name="SELECTED_CLOUD_MATRIX_CONFIGURATION_ID" value="-1" />
+    <option name="SELECTED_CLOUD_MATRIX_PROJECT_ID" value="" />
+    <option name="DEBUGGER_TYPE" value="Auto" />
+    <Auto>
+      <option name="USE_JAVA_AWARE_DEBUGGER" value="false" />
+      <option name="SHOW_STATIC_VARS" value="true" />
+      <option name="WORKING_DIR" value="" />
+      <option name="TARGET_LOGGING_CHANNELS" value="lldb process:gdb-remote packets" />
+      <option name="SHOW_OPTIMIZED_WARNING" value="true" />
+    </Auto>
+    <Hybrid>
+      <option name="USE_JAVA_AWARE_DEBUGGER" value="false" />
+      <option name="SHOW_STATIC_VARS" value="true" />
+      <option name="WORKING_DIR" value="" />
+      <option name="TARGET_LOGGING_CHANNELS" value="lldb process:gdb-remote packets" />
+      <option name="SHOW_OPTIMIZED_WARNING" value="true" />
+    </Hybrid>
+    <Java />
+    <Native>
+      <option name="USE_JAVA_AWARE_DEBUGGER" value="false" />
+      <option name="SHOW_STATIC_VARS" value="true" />
+      <option name="WORKING_DIR" value="" />
+      <option name="TARGET_LOGGING_CHANNELS" value="lldb process:gdb-remote packets" />
+      <option name="SHOW_OPTIMIZED_WARNING" value="true" />
+    </Native>
+    <Profilers>
+      <option name="ENABLE_ADVANCED_PROFILING" value="true" />
+      <option name="SUPPORT_LIB_ENABLED" value="true" />
+      <option name="INSTRUMENTATION_ENABLED" value="true" />
+    </Profilers>
+    <option name="DEEP_LINK" value="" />
+    <option name="ACTIVITY_CLASS" value="com.example.android.lifecycles.step4_solution.LocationActivity" />
+    <method />
+  </configuration>
+</component>
\ No newline at end of file
diff --git a/samples-flatfoot/codelabs/lifecycle/.idea/runConfigurations/Step_5___Sharing_VMs.xml b/samples-flatfoot/codelabs/lifecycle/.idea/runConfigurations/Step_5___Sharing_VMs.xml
new file mode 100644
index 0000000..55b1e43
--- /dev/null
+++ b/samples-flatfoot/codelabs/lifecycle/.idea/runConfigurations/Step_5___Sharing_VMs.xml
@@ -0,0 +1,51 @@
+<component name="ProjectRunConfigurationManager">
+  <configuration default="false" name="Step 5 - Sharing VMs" type="AndroidRunConfigurationType" factoryName="Android App" activateToolWindowBeforeRun="false">
+    <module name="app" />
+    <option name="DEPLOY" value="true" />
+    <option name="ARTIFACT_NAME" value="" />
+    <option name="PM_INSTALL_OPTIONS" value="" />
+    <option name="ACTIVITY_EXTRA_FLAGS" value="" />
+    <option name="MODE" value="specific_activity" />
+    <option name="PREFERRED_AVD" value="" />
+    <option name="CLEAR_LOGCAT" value="false" />
+    <option name="SHOW_LOGCAT_AUTOMATICALLY" value="false" />
+    <option name="SKIP_NOOP_APK_INSTALLATIONS" value="true" />
+    <option name="FORCE_STOP_RUNNING_APP" value="true" />
+    <option name="TARGET_SELECTION_MODE" value="SHOW_DIALOG" />
+    <option name="USE_LAST_SELECTED_DEVICE" value="false" />
+    <option name="PREFERRED_AVD" value="" />
+    <option name="SELECTED_CLOUD_MATRIX_CONFIGURATION_ID" value="-1" />
+    <option name="SELECTED_CLOUD_MATRIX_PROJECT_ID" value="" />
+    <option name="DEBUGGER_TYPE" value="Auto" />
+    <Auto>
+      <option name="USE_JAVA_AWARE_DEBUGGER" value="false" />
+      <option name="SHOW_STATIC_VARS" value="true" />
+      <option name="WORKING_DIR" value="" />
+      <option name="TARGET_LOGGING_CHANNELS" value="lldb process:gdb-remote packets" />
+      <option name="SHOW_OPTIMIZED_WARNING" value="true" />
+    </Auto>
+    <Hybrid>
+      <option name="USE_JAVA_AWARE_DEBUGGER" value="false" />
+      <option name="SHOW_STATIC_VARS" value="true" />
+      <option name="WORKING_DIR" value="" />
+      <option name="TARGET_LOGGING_CHANNELS" value="lldb process:gdb-remote packets" />
+      <option name="SHOW_OPTIMIZED_WARNING" value="true" />
+    </Hybrid>
+    <Java />
+    <Native>
+      <option name="USE_JAVA_AWARE_DEBUGGER" value="false" />
+      <option name="SHOW_STATIC_VARS" value="true" />
+      <option name="WORKING_DIR" value="" />
+      <option name="TARGET_LOGGING_CHANNELS" value="lldb process:gdb-remote packets" />
+      <option name="SHOW_OPTIMIZED_WARNING" value="true" />
+    </Native>
+    <Profilers>
+      <option name="ENABLE_ADVANCED_PROFILING" value="true" />
+      <option name="SUPPORT_LIB_ENABLED" value="true" />
+      <option name="INSTRUMENTATION_ENABLED" value="true" />
+    </Profilers>
+    <option name="DEEP_LINK" value="" />
+    <option name="ACTIVITY_CLASS" value="com.example.android.lifecycles.step5.Activity_step5" />
+    <method />
+  </configuration>
+</component>
\ No newline at end of file
diff --git a/samples-flatfoot/codelabs/lifecycle/.idea/runConfigurations/Step_5___Sharing_VMs_solution.xml b/samples-flatfoot/codelabs/lifecycle/.idea/runConfigurations/Step_5___Sharing_VMs_solution.xml
new file mode 100644
index 0000000..a060505
--- /dev/null
+++ b/samples-flatfoot/codelabs/lifecycle/.idea/runConfigurations/Step_5___Sharing_VMs_solution.xml
@@ -0,0 +1,51 @@
+<component name="ProjectRunConfigurationManager">
+  <configuration default="false" name="Step 5 - Sharing VMs solution" type="AndroidRunConfigurationType" factoryName="Android App" activateToolWindowBeforeRun="false">
+    <module name="app" />
+    <option name="DEPLOY" value="true" />
+    <option name="ARTIFACT_NAME" value="" />
+    <option name="PM_INSTALL_OPTIONS" value="" />
+    <option name="ACTIVITY_EXTRA_FLAGS" value="" />
+    <option name="MODE" value="specific_activity" />
+    <option name="PREFERRED_AVD" value="" />
+    <option name="CLEAR_LOGCAT" value="false" />
+    <option name="SHOW_LOGCAT_AUTOMATICALLY" value="false" />
+    <option name="SKIP_NOOP_APK_INSTALLATIONS" value="true" />
+    <option name="FORCE_STOP_RUNNING_APP" value="true" />
+    <option name="TARGET_SELECTION_MODE" value="SHOW_DIALOG" />
+    <option name="USE_LAST_SELECTED_DEVICE" value="false" />
+    <option name="PREFERRED_AVD" value="" />
+    <option name="SELECTED_CLOUD_MATRIX_CONFIGURATION_ID" value="-1" />
+    <option name="SELECTED_CLOUD_MATRIX_PROJECT_ID" value="" />
+    <option name="DEBUGGER_TYPE" value="Auto" />
+    <Auto>
+      <option name="USE_JAVA_AWARE_DEBUGGER" value="false" />
+      <option name="SHOW_STATIC_VARS" value="true" />
+      <option name="WORKING_DIR" value="" />
+      <option name="TARGET_LOGGING_CHANNELS" value="lldb process:gdb-remote packets" />
+      <option name="SHOW_OPTIMIZED_WARNING" value="true" />
+    </Auto>
+    <Hybrid>
+      <option name="USE_JAVA_AWARE_DEBUGGER" value="false" />
+      <option name="SHOW_STATIC_VARS" value="true" />
+      <option name="WORKING_DIR" value="" />
+      <option name="TARGET_LOGGING_CHANNELS" value="lldb process:gdb-remote packets" />
+      <option name="SHOW_OPTIMIZED_WARNING" value="true" />
+    </Hybrid>
+    <Java />
+    <Native>
+      <option name="USE_JAVA_AWARE_DEBUGGER" value="false" />
+      <option name="SHOW_STATIC_VARS" value="true" />
+      <option name="WORKING_DIR" value="" />
+      <option name="TARGET_LOGGING_CHANNELS" value="lldb process:gdb-remote packets" />
+      <option name="SHOW_OPTIMIZED_WARNING" value="true" />
+    </Native>
+    <Profilers>
+      <option name="ENABLE_ADVANCED_PROFILING" value="true" />
+      <option name="SUPPORT_LIB_ENABLED" value="true" />
+      <option name="INSTRUMENTATION_ENABLED" value="true" />
+    </Profilers>
+    <option name="DEEP_LINK" value="" />
+    <option name="ACTIVITY_CLASS" value="com.example.android.lifecycles.step5_solution.Activity_step5" />
+    <method />
+  </configuration>
+</component>
\ No newline at end of file
diff --git a/samples-flatfoot/codelabs/lifecycle/CONTRIBUTING.md b/samples-flatfoot/codelabs/lifecycle/CONTRIBUTING.md
new file mode 100644
index 0000000..7b86f95
--- /dev/null
+++ b/samples-flatfoot/codelabs/lifecycle/CONTRIBUTING.md
@@ -0,0 +1,35 @@
+# How to become a contributor and submit your own code
+
+## Contributor License Agreements
+
+We'd love to accept your sample apps and patches! Before we can take them, we
+have to jump a couple of legal hurdles.
+
+Please fill out either the individual or corporate Contributor License Agreement (CLA).
+
+  * If you are an individual writing original source code and you're sure you
+    own the intellectual property, then you'll need to sign an [individual CLA]
+    (https://cla.developers.google.com).
+  * If you work for a company that wants to allow you to contribute your work,
+    then you'll need to sign a [corporate CLA]
+    (https://cla.developers.google.com).
+  * Please make sure you sign both, Android and Google CLA
+
+Follow either of the two links above to access the appropriate CLA and
+instructions for how to sign and return it. Once we receive it, we'll be able to
+accept your pull requests.
+
+## Contributing A Patch
+
+1. Submit an issue describing your proposed change to the repo in question.
+1. The repo owner will respond to your issue promptly.
+1. If your proposed change is accepted, and you haven't already done so, sign a
+   Contributor License Agreement (see details above).
+1. Fork the desired repo, develop and test your code changes.
+1. Ensure that your code adheres to the existing style in the sample to which
+   you are contributing. Refer to the
+   [Android Code Style Guide]
+   (https://source.android.com/source/code-style.html) for the
+   recommended coding standards for this organization.
+1. Ensure that your code has an appropriate set of unit tests which all pass.
+1. Submit a pull request.
diff --git a/samples-flatfoot/codelabs/lifecycle/LICENSE b/samples-flatfoot/codelabs/lifecycle/LICENSE
new file mode 100644
index 0000000..1af981f
--- /dev/null
+++ b/samples-flatfoot/codelabs/lifecycle/LICENSE
@@ -0,0 +1,201 @@
+                                 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
+
+   APPENDIX: How to apply the Apache License to your work.
+
+      To apply the Apache License to your work, attach the following
+      boilerplate notice, with the fields enclosed by brackets "[]"
+      replaced with your own identifying information. (Don't include
+      the brackets!)  The text should be enclosed in the appropriate
+      comment syntax for the file format. We also recommend that a
+      file or class name and description of purpose be included on the
+      same "printed page" as the copyright notice for easier
+      identification within third-party archives.
+
+   Copyright 2014 The Android Open Source Project
+
+   Licensed under the Apache License, Version 2.0 (the "License");
+   you may not use this file except in compliance with the License.
+   You may obtain a copy of the License at
+
+       http://www.apache.org/licenses/LICENSE-2.0
+
+   Unless required by applicable law or agreed to in writing, software
+   distributed under the License is distributed on an "AS IS" BASIS,
+   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+   See the License for the specific language governing permissions and
+   limitations under the License.
diff --git a/samples-flatfoot/codelabs/lifecycle/app/build.gradle b/samples-flatfoot/codelabs/lifecycle/app/build.gradle
new file mode 100644
index 0000000..7a9e955
--- /dev/null
+++ b/samples-flatfoot/codelabs/lifecycle/app/build.gradle
@@ -0,0 +1,56 @@
+/*
+ * Copyright 2017, The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+apply plugin: 'com.android.application'
+
+android {
+    compileSdkVersion 25
+    buildToolsVersion "25.0.2"
+    defaultConfig {
+        applicationId 'com.example.android.codelabs.lifecycle'
+        minSdkVersion 21
+        targetSdkVersion 25
+        versionCode 1
+        versionName "1.0"
+        testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
+    }
+    buildTypes {
+        release {
+            minifyEnabled false
+            proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
+        }
+    }
+    dataBinding {
+        enabled = true
+    }
+    productFlavors {
+    }
+}
+
+dependencies {
+    compile fileTree(include: ['*.jar'], dir: 'libs')
+    androidTestCompile('com.android.support.test.espresso:espresso-core:' + rootProject.espressoVersion, {
+        exclude group: 'com.android.support', module: 'support-annotations'
+    })
+    compile 'com.android.support:appcompat-v7:' + rootProject.supportLibVersion;
+    compile 'com.android.support:cardview-v7:' + rootProject.supportLibVersion;
+    compile 'com.android.support:recyclerview-v7:' + rootProject.supportLibVersion;
+    testCompile 'junit:junit:4.12'
+    compile 'android.arch.lifecycle:extensions:' + rootProject.archLifecycleVersion;
+    compile 'android.arch.persistence.room:runtime:' + rootProject.archRoomVersion;
+    annotationProcessor 'android.arch.lifecycle:compiler:' + rootProject.archLifecycleVersion;
+    annotationProcessor 'android.arch.persistence.room:compiler:' + rootProject.archRoomVersion;
+}
diff --git a/samples-flatfoot/codelabs/lifecycle/app/proguard-rules.pro b/samples-flatfoot/codelabs/lifecycle/app/proguard-rules.pro
new file mode 100644
index 0000000..4cb7103
--- /dev/null
+++ b/samples-flatfoot/codelabs/lifecycle/app/proguard-rules.pro
@@ -0,0 +1,17 @@
+# Add project specific ProGuard rules here.
+# By default, the flags in this file are appended to flags specified
+# in /usr/local/google/home/jalc/sw/android-sdks/android-sdk-linux/tools/proguard/proguard-android.txt
+# You can edit the include path and order by changing the proguardFiles
+# directive in build.gradle.
+#
+# For more details, see
+#   http://developer.android.com/guide/developing/tools/proguard.html
+
+# Add any project specific keep options here:
+
+# If your project uses WebView with JS, uncomment the following
+# and specify the fully qualified class name to the JavaScript interface
+# class:
+#-keepclassmembers class fqcn.of.javascript.interface.for.webview {
+#   public *;
+#}
diff --git a/samples-flatfoot/codelabs/lifecycle/app/src/main/AndroidManifest.xml b/samples-flatfoot/codelabs/lifecycle/app/src/main/AndroidManifest.xml
new file mode 100644
index 0000000..1327ac7
--- /dev/null
+++ b/samples-flatfoot/codelabs/lifecycle/app/src/main/AndroidManifest.xml
@@ -0,0 +1,74 @@
+<?xml version="1.0" encoding="utf-8"?><!--
+  ~ Copyright 2017, The Android Open Source Project
+  ~
+  ~ Licensed under the Apache License, Version 2.0 (the "License");
+  ~ you may not use this file except in compliance with the License.
+  ~ You may obtain a copy of the License at
+  ~
+  ~      http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing, software
+  ~ distributed under the License is distributed on an "AS IS" BASIS,
+  ~ WITHOUT 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.codelabs.lifecycle">
+
+    <uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
+    <uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" />
+
+    <uses-feature android:name="android.hardware.location.gps" />
+
+    <application
+        android:allowBackup="true"
+        android:icon="@mipmap/ic_launcher"
+        android:label="@string/app_name"
+        android:supportsRtl="true"
+        android:theme="@style/AppTheme">
+        <activity android:name="com.example.android.lifecycles.step1.ChronoActivity1">
+            <intent-filter>
+                <action android:name="android.intent.action.MAIN" />
+            </intent-filter>
+        </activity>
+        <activity android:name="com.example.android.lifecycles.step2.ChronoActivity2">
+            <intent-filter>
+                <action android:name="android.intent.action.MAIN" />
+            </intent-filter>
+        </activity>
+        <activity android:name="com.example.android.lifecycles.step3.ChronoActivity3">
+            <intent-filter>
+                <action android:name="android.intent.action.MAIN" />
+            </intent-filter>
+        </activity>
+        <activity android:name="com.example.android.lifecycles.step3_solution.ChronoActivity3">
+            <intent-filter>
+                <action android:name="android.intent.action.MAIN" />
+            </intent-filter>
+        </activity>
+        <activity android:name="com.example.android.lifecycles.step4_solution.LocationActivity">
+            <intent-filter>
+                <action android:name="android.intent.action.MAIN" />
+            </intent-filter>
+        </activity>
+        <activity android:name="com.example.android.lifecycles.step4.LocationActivity">
+            <intent-filter>
+                <action android:name="android.intent.action.MAIN" />
+            </intent-filter>
+        </activity>
+        <activity android:name="com.example.android.lifecycles.step5.Activity_step5">
+            <intent-filter>
+                <action android:name="android.intent.action.MAIN" />
+            </intent-filter>
+        </activity>
+        <activity android:name="com.example.android.lifecycles.step5_solution.Activity_step5">
+            <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-flatfoot/codelabs/lifecycle/app/src/main/java/com/example/android/lifecycles/step1/ChronoActivity1.java b/samples-flatfoot/codelabs/lifecycle/app/src/main/java/com/example/android/lifecycles/step1/ChronoActivity1.java
new file mode 100644
index 0000000..d7dcf59
--- /dev/null
+++ b/samples-flatfoot/codelabs/lifecycle/app/src/main/java/com/example/android/lifecycles/step1/ChronoActivity1.java
@@ -0,0 +1,37 @@
+/*
+ * Copyright 2017, The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES 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.lifecycles.step1;
+
+import android.os.Bundle;
+import android.support.v7.app.AppCompatActivity;
+import android.widget.Chronometer;
+
+import com.example.android.codelabs.lifecycle.R;
+
+
+public class ChronoActivity1 extends AppCompatActivity {
+
+    @Override
+    protected void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+        setContentView(R.layout.activity_main);
+
+        Chronometer chronometer = (Chronometer) findViewById(R.id.chronometer);
+
+        chronometer.start();
+    }
+}
diff --git a/samples-flatfoot/codelabs/lifecycle/app/src/main/java/com/example/android/lifecycles/step2/ChronoActivity2.java b/samples-flatfoot/codelabs/lifecycle/app/src/main/java/com/example/android/lifecycles/step2/ChronoActivity2.java
new file mode 100644
index 0000000..4ef5e23
--- /dev/null
+++ b/samples-flatfoot/codelabs/lifecycle/app/src/main/java/com/example/android/lifecycles/step2/ChronoActivity2.java
@@ -0,0 +1,54 @@
+/*
+ * Copyright 2017, The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES 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.lifecycles.step2;
+
+import android.os.Bundle;
+import android.os.SystemClock;
+import android.widget.Chronometer;
+
+import android.arch.lifecycle.LifecycleActivity;
+import android.arch.lifecycle.ViewModelProviders;
+import com.example.android.codelabs.lifecycle.R;
+
+public class ChronoActivity2 extends LifecycleActivity {
+
+    @Override
+    protected void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+        setContentView(R.layout.activity_main);
+
+        // The ViewModelStore provides a new ViewModel or one previously created.
+        ChronometerViewModel chronometerViewModel
+                = ViewModelProviders.of(this).get(ChronometerViewModel.class);
+
+        // Get the chronometer reference
+        Chronometer chronometer = (Chronometer) findViewById(R.id.chronometer);
+
+        if (chronometerViewModel.getStartDate() == null) {
+            // If the start date is not defined, it's a new ViewModel so set it.
+            long startTime = SystemClock.elapsedRealtime();
+            chronometerViewModel.setStartDate(startTime);
+            chronometer.setBase(startTime);
+        } else {
+            // Otherwise the ViewModel has been retained, set the chronometer's base to the original
+            // starting time.
+            chronometer.setBase(chronometerViewModel.getStartDate());
+        }
+
+        chronometer.start();
+    }
+}
diff --git a/samples-flatfoot/codelabs/lifecycle/app/src/main/java/com/example/android/lifecycles/step2/ChronometerViewModel.java b/samples-flatfoot/codelabs/lifecycle/app/src/main/java/com/example/android/lifecycles/step2/ChronometerViewModel.java
new file mode 100644
index 0000000..136ed50
--- /dev/null
+++ b/samples-flatfoot/codelabs/lifecycle/app/src/main/java/com/example/android/lifecycles/step2/ChronometerViewModel.java
@@ -0,0 +1,39 @@
+/*
+ * Copyright 2017, The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES 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.lifecycles.step2;
+
+import android.support.annotation.Nullable;
+
+import android.arch.lifecycle.ViewModel;
+
+/**
+ * A ViewModel used for the {@link ChronoActivity2}.
+ */
+public class ChronometerViewModel extends ViewModel {
+
+    @Nullable
+    private Long startDate;
+
+    @Nullable
+    public Long getStartDate() {
+        return startDate;
+    }
+
+    public void setStartDate(final long startDate) {
+        this.startDate = startDate;
+    }
+}
diff --git a/samples-flatfoot/codelabs/lifecycle/app/src/main/java/com/example/android/lifecycles/step3/ChronoActivity3.java b/samples-flatfoot/codelabs/lifecycle/app/src/main/java/com/example/android/lifecycles/step3/ChronoActivity3.java
new file mode 100644
index 0000000..4ff1a87
--- /dev/null
+++ b/samples-flatfoot/codelabs/lifecycle/app/src/main/java/com/example/android/lifecycles/step3/ChronoActivity3.java
@@ -0,0 +1,57 @@
+/*
+ * Copyright 2017, The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES 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.lifecycles.step3;
+
+import android.os.Bundle;
+import android.support.annotation.Nullable;
+import android.util.Log;
+import android.widget.TextView;
+
+import android.arch.lifecycle.LifecycleActivity;
+import android.arch.lifecycle.Observer;
+import android.arch.lifecycle.ViewModelProviders;
+import com.example.android.codelabs.lifecycle.R;
+
+
+public class ChronoActivity3 extends LifecycleActivity {
+
+    private LiveDataTimerViewModel chronometerViewModel;
+
+    @Override
+    protected void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+
+        setContentView(R.layout.chrono_activity_3);
+
+        chronometerViewModel = ViewModelProviders.of(this).get(LiveDataTimerViewModel.class);
+
+        subscribe();
+    }
+
+    private void subscribe() {
+        final Observer<Long> elapsedTimeObserver = new Observer<Long>() {
+            @Override
+            public void onChanged(@Nullable final Long aLong) {
+                String newText = ChronoActivity3.this.getResources().getString(R.string.seconds, aLong);
+                ((TextView) findViewById(R.id.timer_textview)).setText(newText);
+                Log.d("ChronoActivity3", "Updating timer");
+            }
+        };
+
+        //TODO: observe the ViewModel's elapsed time
+    }
+}
diff --git a/samples-flatfoot/codelabs/lifecycle/app/src/main/java/com/example/android/lifecycles/step3/LiveDataTimerViewModel.java b/samples-flatfoot/codelabs/lifecycle/app/src/main/java/com/example/android/lifecycles/step3/LiveDataTimerViewModel.java
new file mode 100644
index 0000000..cc88d7c
--- /dev/null
+++ b/samples-flatfoot/codelabs/lifecycle/app/src/main/java/com/example/android/lifecycles/step3/LiveDataTimerViewModel.java
@@ -0,0 +1,69 @@
+/*
+ * Copyright 2017, The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES 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.lifecycles.step3;
+
+import android.arch.lifecycle.MutableLiveData;
+import android.os.Handler;
+import android.os.Looper;
+import android.os.SystemClock;
+
+import android.arch.lifecycle.LiveData;
+import android.arch.lifecycle.ViewModel;
+
+import java.util.Timer;
+import java.util.TimerTask;
+
+/**
+ * A ViewModel used for the {@link ChronoActivity3}.
+ */
+public class LiveDataTimerViewModel extends ViewModel {
+
+    private static final int ONE_SECOND = 1000;
+
+    private MutableLiveData<Long> elapsedTime = new MutableLiveData<>();
+
+    private long mInitialTime;
+
+    public LiveDataTimerViewModel() {
+        mInitialTime = SystemClock.elapsedRealtime();
+        Timer timer = new Timer();
+
+        // Update the elapsed time every second.
+        timer.scheduleAtFixedRate(new TimerTask() {
+            @Override
+            public void run() {
+                final long newValue = (SystemClock.elapsedRealtime() - mInitialTime) / 1000;
+
+                // setValue() cannot be called from a background thread so post to main thread.
+                new Handler(Looper.getMainLooper()).post(new Runnable() {
+                    @Override
+                    public void run() {
+
+                        //TODO set the new value
+
+                    }
+                });
+            }
+        }, ONE_SECOND, ONE_SECOND);
+
+    }
+
+    @SuppressWarnings("unused")  // Will be used when step is completed
+    public LiveData<Long> getElapsedTime() {
+        return elapsedTime;
+    }
+}
diff --git a/samples-flatfoot/codelabs/lifecycle/app/src/main/java/com/example/android/lifecycles/step3_solution/ChronoActivity3.java b/samples-flatfoot/codelabs/lifecycle/app/src/main/java/com/example/android/lifecycles/step3_solution/ChronoActivity3.java
new file mode 100644
index 0000000..2caf5fb
--- /dev/null
+++ b/samples-flatfoot/codelabs/lifecycle/app/src/main/java/com/example/android/lifecycles/step3_solution/ChronoActivity3.java
@@ -0,0 +1,56 @@
+/*
+ * Copyright 2017, The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES 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.lifecycles.step3_solution;
+
+import android.os.Bundle;
+import android.support.annotation.Nullable;
+import android.util.Log;
+import android.widget.TextView;
+
+import android.arch.lifecycle.LifecycleActivity;
+import android.arch.lifecycle.Observer;
+import android.arch.lifecycle.ViewModelProviders;
+import com.example.android.codelabs.lifecycle.R;
+
+public class ChronoActivity3 extends LifecycleActivity {
+
+    private LiveDataTimerViewModel chronometerViewModel;
+
+    @Override
+    protected void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+
+        setContentView(R.layout.chrono_activity_3);
+
+        chronometerViewModel = ViewModelProviders.of(this).get(LiveDataTimerViewModel.class);
+
+        subscribe();
+    }
+
+    private void subscribe() {
+        final Observer<Long> elapsedTimeObserver = new Observer<Long>() {
+            @Override
+            public void onChanged(@Nullable final Long aLong) {
+                String newText = ChronoActivity3.this.getResources().getString(R.string.seconds, aLong);
+                ((TextView) findViewById(R.id.timer_textview)).setText(newText);
+                Log.d("ChronoActivity3", "Updating timer");
+            }
+        };
+
+        chronometerViewModel.getElapsedTime().observe(this, elapsedTimeObserver);
+    }
+}
diff --git a/samples-flatfoot/codelabs/lifecycle/app/src/main/java/com/example/android/lifecycles/step3_solution/LiveDataTimerViewModel.java b/samples-flatfoot/codelabs/lifecycle/app/src/main/java/com/example/android/lifecycles/step3_solution/LiveDataTimerViewModel.java
new file mode 100644
index 0000000..2644415
--- /dev/null
+++ b/samples-flatfoot/codelabs/lifecycle/app/src/main/java/com/example/android/lifecycles/step3_solution/LiveDataTimerViewModel.java
@@ -0,0 +1,66 @@
+/*
+ * Copyright 2017, The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES 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.lifecycles.step3_solution;
+
+import android.arch.lifecycle.MutableLiveData;
+import android.os.Handler;
+import android.os.Looper;
+import android.os.SystemClock;
+
+import android.arch.lifecycle.LiveData;
+import android.arch.lifecycle.ViewModel;
+
+import java.util.Timer;
+import java.util.TimerTask;
+
+/**
+ * A ViewModel used for the {@link ChronoActivity3}.
+ */
+public class LiveDataTimerViewModel extends ViewModel {
+
+    private static final int ONE_SECOND = 1000;
+
+    private MutableLiveData<Long> elapsedTime = new MutableLiveData<>();
+
+    private long mInitialTime;
+
+    public LiveDataTimerViewModel() {
+        mInitialTime = SystemClock.elapsedRealtime();
+        Timer timer = new Timer();
+
+        // Update the elapsed time every second.
+        timer.scheduleAtFixedRate(new TimerTask() {
+            @Override
+            public void run() {
+                final long newValue = (SystemClock.elapsedRealtime() - mInitialTime) / 1000;
+                // setValue() cannot be called from a background thread so post to main thread.
+                new Handler(Looper.getMainLooper()).post(new Runnable() {
+                    @Override
+                    public void run() {
+                        elapsedTime.setValue(newValue);
+
+                    }
+                });
+            }
+        }, ONE_SECOND, ONE_SECOND);
+
+    }
+
+    public LiveData<Long> getElapsedTime() {
+        return elapsedTime;
+    }
+}
diff --git a/samples-flatfoot/codelabs/lifecycle/app/src/main/java/com/example/android/lifecycles/step4/BoundLocationManager.java b/samples-flatfoot/codelabs/lifecycle/app/src/main/java/com/example/android/lifecycles/step4/BoundLocationManager.java
new file mode 100644
index 0000000..ad38c4a
--- /dev/null
+++ b/samples-flatfoot/codelabs/lifecycle/app/src/main/java/com/example/android/lifecycles/step4/BoundLocationManager.java
@@ -0,0 +1,76 @@
+/*
+ * Copyright 2017, The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES 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.lifecycles.step4;
+
+import android.content.Context;
+import android.location.Location;
+import android.location.LocationListener;
+import android.location.LocationManager;
+import android.util.Log;
+
+import android.arch.lifecycle.LifecycleObserver;
+import android.arch.lifecycle.LifecycleRegistryOwner;
+
+
+public class BoundLocationManager {
+    public static void bindLocationListenerIn(LifecycleRegistryOwner lifecycleOwner,
+                                              LocationListener listener, Context context) {
+        new BoundLocationListener(lifecycleOwner, listener, context);
+    }
+
+    @SuppressWarnings("MissingPermission")
+    static class BoundLocationListener implements LifecycleObserver {
+        private final Context mContext;
+        private LocationManager mLocationManager;
+        private final LocationListener mListener;
+
+        public BoundLocationListener(LifecycleRegistryOwner lifecycleOwner,
+                                     LocationListener listener, Context context) {
+            mContext = context;
+            mListener = listener;
+            //TODO: Add lifecycle observer
+        }
+
+        //TODO: Call this on resume
+        void addLocationListener() {
+            // Note: Use the Fused Location Provider from Google Play Services instead.
+            // https://developers.google.com/android/reference/com/google/android/gms/location/FusedLocationProviderApi
+
+            mLocationManager =
+                    (LocationManager) mContext.getSystemService(Context.LOCATION_SERVICE);
+            mLocationManager.requestLocationUpdates(LocationManager.GPS_PROVIDER, 0, 0, mListener);
+            Log.d("BoundLocationMgr", "Listener added");
+
+            // Force an update with the last location, if available.
+            Location lastLocation = mLocationManager.getLastKnownLocation(
+                    LocationManager.GPS_PROVIDER);
+            if (lastLocation != null) {
+                mListener.onLocationChanged(lastLocation);
+            }
+        }
+
+        //TODO: Call this on pause
+        void removeLocationListener() {
+            if (mLocationManager == null) {
+                return;
+            }
+            mLocationManager.removeUpdates(mListener);
+            mLocationManager = null;
+            Log.d("BoundLocationMgr", "Listener removed");
+        }
+    }
+}
diff --git a/samples-flatfoot/codelabs/lifecycle/app/src/main/java/com/example/android/lifecycles/step4/LocationActivity.java b/samples-flatfoot/codelabs/lifecycle/app/src/main/java/com/example/android/lifecycles/step4/LocationActivity.java
new file mode 100644
index 0000000..4712267
--- /dev/null
+++ b/samples-flatfoot/codelabs/lifecycle/app/src/main/java/com/example/android/lifecycles/step4/LocationActivity.java
@@ -0,0 +1,92 @@
+/*
+ * Copyright 2017, The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES 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.lifecycles.step4;
+
+import android.Manifest;
+import android.content.pm.PackageManager;
+import android.location.Location;
+import android.location.LocationListener;
+import android.os.Bundle;
+import android.support.annotation.NonNull;
+import android.support.v4.app.ActivityCompat;
+import android.widget.TextView;
+import android.widget.Toast;
+
+import android.arch.lifecycle.LifecycleActivity;
+import com.example.android.codelabs.lifecycle.R;
+
+public class LocationActivity extends LifecycleActivity {
+
+    private static final int REQUEST_LOCATION_PERMISSION_CODE = 1;
+
+    private LocationListener mGpsListener = new MyLocationListener();
+
+    @Override
+    public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions,
+            @NonNull int[] grantResults) {
+        super.onRequestPermissionsResult(requestCode, permissions, grantResults);
+        if (grantResults[0] == PackageManager.PERMISSION_GRANTED
+                && grantResults[1] == PackageManager.PERMISSION_GRANTED) {
+            bindLocationListener();
+        } else {
+            Toast.makeText(this, "This sample requires Location access", Toast.LENGTH_LONG).show();
+        }
+    }
+
+    private void bindLocationListener() {
+        BoundLocationManager.bindLocationListenerIn(this, mGpsListener, getApplicationContext());
+    }
+
+    @Override
+    protected void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+        setContentView(R.layout.location_activity);
+
+        if (ActivityCompat.checkSelfPermission(this, Manifest.permission.ACCESS_FINE_LOCATION)
+                != PackageManager.PERMISSION_GRANTED && ActivityCompat.checkSelfPermission(this,
+                Manifest.permission.ACCESS_COARSE_LOCATION) != PackageManager.PERMISSION_GRANTED) {
+            ActivityCompat.requestPermissions(this,
+                    new String[]{Manifest.permission.ACCESS_FINE_LOCATION,
+                            Manifest.permission.ACCESS_COARSE_LOCATION},
+                    REQUEST_LOCATION_PERMISSION_CODE);
+        } else {
+            bindLocationListener();
+        }
+    }
+
+    private class MyLocationListener implements LocationListener {
+        @Override
+        public void onLocationChanged(Location location) {
+            TextView textView = (TextView) findViewById(R.id.location);
+            textView.setText(location.getLatitude() + ", " + location.getLongitude());
+        }
+
+        @Override
+        public void onStatusChanged(String provider, int status, Bundle extras) {
+        }
+
+        @Override
+        public void onProviderEnabled(String provider) {
+            Toast.makeText(LocationActivity.this,
+                    "Provider enabled: " + provider, Toast.LENGTH_SHORT).show();
+        }
+
+        @Override
+        public void onProviderDisabled(String provider) {
+        }
+    }
+}
diff --git a/samples-flatfoot/codelabs/lifecycle/app/src/main/java/com/example/android/lifecycles/step4_solution/BoundLocationManager.java b/samples-flatfoot/codelabs/lifecycle/app/src/main/java/com/example/android/lifecycles/step4_solution/BoundLocationManager.java
new file mode 100644
index 0000000..4360c2a
--- /dev/null
+++ b/samples-flatfoot/codelabs/lifecycle/app/src/main/java/com/example/android/lifecycles/step4_solution/BoundLocationManager.java
@@ -0,0 +1,77 @@
+/*
+ * Copyright 2017, The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES 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.lifecycles.step4_solution;
+
+import android.arch.lifecycle.Lifecycle;
+import android.arch.lifecycle.LifecycleObserver;
+import android.arch.lifecycle.LifecycleOwner;
+import android.arch.lifecycle.OnLifecycleEvent;
+import android.content.Context;
+import android.location.Location;
+import android.location.LocationListener;
+import android.location.LocationManager;
+import android.util.Log;
+
+public class BoundLocationManager {
+    public static void bindLocationListenerIn(LifecycleOwner lifecycleOwner,
+                                              LocationListener listener, Context context) {
+        new BoundLocationListener(lifecycleOwner, listener, context);
+    }
+
+    @SuppressWarnings("MissingPermission")
+    static class BoundLocationListener implements LifecycleObserver {
+        private final Context mContext;
+        private LocationManager mLocationManager;
+        private final LocationListener mListener;
+
+        public BoundLocationListener(LifecycleOwner lifecycleOwner,
+                                     LocationListener listener, Context context) {
+            mContext = context;
+            mListener = listener;
+            lifecycleOwner.getLifecycle().addObserver(this);
+        }
+
+        @OnLifecycleEvent(Lifecycle.Event.ON_RESUME)
+        void addLocationListener() {
+            // Note: Use the Fused Location Provider from Google Play Services instead.
+            // https://developers.google.com/android/reference/com/google/android/gms/location/FusedLocationProviderApi
+
+            mLocationManager =
+                    (LocationManager) mContext.getSystemService(Context.LOCATION_SERVICE);
+            mLocationManager.requestLocationUpdates(LocationManager.GPS_PROVIDER, 0, 0, mListener);
+            Log.d("BoundLocationMgr", "Listener added");
+
+            // Force an update with the last location, if available.
+            Location lastLocation = mLocationManager.getLastKnownLocation(
+                    LocationManager.GPS_PROVIDER);
+            if (lastLocation != null) {
+                mListener.onLocationChanged(lastLocation);
+            }
+        }
+
+
+        @OnLifecycleEvent(Lifecycle.Event.ON_PAUSE)
+        void removeLocationListener() {
+            if (mLocationManager == null) {
+                return;
+            }
+            mLocationManager.removeUpdates(mListener);
+            mLocationManager = null;
+            Log.d("BoundLocationMgr", "Listener removed");
+        }
+    }
+}
diff --git a/samples-flatfoot/codelabs/lifecycle/app/src/main/java/com/example/android/lifecycles/step4_solution/LocationActivity.java b/samples-flatfoot/codelabs/lifecycle/app/src/main/java/com/example/android/lifecycles/step4_solution/LocationActivity.java
new file mode 100644
index 0000000..79ff562
--- /dev/null
+++ b/samples-flatfoot/codelabs/lifecycle/app/src/main/java/com/example/android/lifecycles/step4_solution/LocationActivity.java
@@ -0,0 +1,92 @@
+/*
+ * Copyright 2017, The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES 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.lifecycles.step4_solution;
+
+import android.Manifest;
+import android.content.pm.PackageManager;
+import android.location.Location;
+import android.location.LocationListener;
+import android.os.Bundle;
+import android.support.annotation.NonNull;
+import android.support.v4.app.ActivityCompat;
+import android.widget.TextView;
+import android.widget.Toast;
+
+import android.arch.lifecycle.LifecycleActivity;
+import com.example.android.codelabs.lifecycle.R;
+
+public class LocationActivity extends LifecycleActivity {
+
+    private static final int REQUEST_LOCATION_PERMISSION_CODE = 1;
+
+    private LocationListener mGpsListener = new MyLocationListener();
+
+    @Override
+    public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions,
+            @NonNull int[] grantResults) {
+        super.onRequestPermissionsResult(requestCode, permissions, grantResults);
+        if (grantResults[0] == PackageManager.PERMISSION_GRANTED
+                && grantResults[1] == PackageManager.PERMISSION_GRANTED) {
+            bindLocationListener();
+        } else {
+            Toast.makeText(this, "This sample requires Location access", Toast.LENGTH_LONG).show();
+        }
+    }
+
+    private void bindLocationListener() {
+        BoundLocationManager.bindLocationListenerIn(this, mGpsListener, getApplicationContext());
+    }
+
+    @Override
+    protected void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+        setContentView(R.layout.location_activity);
+
+        if (ActivityCompat.checkSelfPermission(this, Manifest.permission.ACCESS_FINE_LOCATION)
+                != PackageManager.PERMISSION_GRANTED && ActivityCompat.checkSelfPermission(this,
+                Manifest.permission.ACCESS_COARSE_LOCATION) != PackageManager.PERMISSION_GRANTED) {
+            ActivityCompat.requestPermissions(this,
+                    new String[]{Manifest.permission.ACCESS_FINE_LOCATION,
+                            Manifest.permission.ACCESS_COARSE_LOCATION},
+                    REQUEST_LOCATION_PERMISSION_CODE);
+        } else {
+            bindLocationListener();
+        }
+    }
+
+    private class MyLocationListener implements LocationListener {
+        @Override
+        public void onLocationChanged(Location location) {
+            TextView textView = (TextView) findViewById(R.id.location);
+            textView.setText(location.getLatitude() + ", " + location.getLongitude());
+        }
+
+        @Override
+        public void onStatusChanged(String provider, int status, Bundle extras) {
+        }
+
+        @Override
+        public void onProviderEnabled(String provider) {
+            Toast.makeText(LocationActivity.this,
+                    "Provider enabled: " + provider, Toast.LENGTH_SHORT).show();
+        }
+
+        @Override
+        public void onProviderDisabled(String provider) {
+        }
+    }
+}
diff --git a/samples-flatfoot/codelabs/lifecycle/app/src/main/java/com/example/android/lifecycles/step5/Activity_step5.java b/samples-flatfoot/codelabs/lifecycle/app/src/main/java/com/example/android/lifecycles/step5/Activity_step5.java
new file mode 100644
index 0000000..ec6df9a
--- /dev/null
+++ b/samples-flatfoot/codelabs/lifecycle/app/src/main/java/com/example/android/lifecycles/step5/Activity_step5.java
@@ -0,0 +1,35 @@
+/*
+ * Copyright 2017, The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES 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.lifecycles.step5;
+
+import android.os.Bundle;
+
+import android.arch.lifecycle.LifecycleActivity;
+import com.example.android.codelabs.lifecycle.R;
+
+/**
+ * Shows two {@link Fragment_step5} fragments.
+ */
+public class Activity_step5 extends LifecycleActivity {
+
+    @Override
+    protected void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+
+        setContentView(R.layout.activity_step5);
+    }
+}
diff --git a/samples-flatfoot/codelabs/lifecycle/app/src/main/java/com/example/android/lifecycles/step5/Fragment_step5.java b/samples-flatfoot/codelabs/lifecycle/app/src/main/java/com/example/android/lifecycles/step5/Fragment_step5.java
new file mode 100644
index 0000000..1b580ea
--- /dev/null
+++ b/samples-flatfoot/codelabs/lifecycle/app/src/main/java/com/example/android/lifecycles/step5/Fragment_step5.java
@@ -0,0 +1,71 @@
+/*
+ * Copyright 2017, The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES 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.lifecycles.step5;
+
+
+import android.os.Bundle;
+import android.support.v4.app.Fragment;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.SeekBar;
+
+import com.example.android.codelabs.lifecycle.R;
+
+/**
+ * Shows a SeekBar that should be synced with a value in a ViewModel.
+ */
+public class Fragment_step5 extends Fragment {
+
+    private SeekBar mSeekBar;
+
+    private SeekBarViewModel mSeekBarViewModel;
+
+    @Override
+    public View onCreateView(LayoutInflater inflater, ViewGroup container,
+                             Bundle savedInstanceState) {
+        // Inflate the layout for this fragment
+        View root = inflater.inflate(R.layout.fragment_step5, container, false);
+        mSeekBar = (SeekBar) root.findViewById(R.id.seekBar);
+
+        // TODO: get ViewModel
+        subscribeSeekBar();
+
+        return root;
+    }
+
+    private void subscribeSeekBar() {
+
+        // Update the ViewModel when the SeekBar is changed.
+
+        mSeekBar.setOnSeekBarChangeListener(new SeekBar.OnSeekBarChangeListener() {
+            @Override
+            public void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser) {
+                // TODO: Set the ViewModel's value when the change comes from the user.
+            }
+
+            @Override
+            public void onStartTrackingTouch(SeekBar seekBar) { }
+
+            @Override
+            public void onStopTrackingTouch(SeekBar seekBar) { }
+        });
+
+        // TODO: Update the SeekBar when the ViewModel is changed.
+        // mSeekBarViewModel.seekbarValue.observe(...
+    }
+}
diff --git a/samples-flatfoot/codelabs/lifecycle/app/src/main/java/com/example/android/lifecycles/step5/SeekBarViewModel.java b/samples-flatfoot/codelabs/lifecycle/app/src/main/java/com/example/android/lifecycles/step5/SeekBarViewModel.java
new file mode 100644
index 0000000..5811a19
--- /dev/null
+++ b/samples-flatfoot/codelabs/lifecycle/app/src/main/java/com/example/android/lifecycles/step5/SeekBarViewModel.java
@@ -0,0 +1,28 @@
+/*
+ * Copyright 2017, The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES 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.lifecycles.step5;
+
+import android.arch.lifecycle.MutableLiveData;
+import android.arch.lifecycle.ViewModel;
+
+/**
+ * A ViewModel used in step 5.
+ */
+public class SeekBarViewModel extends ViewModel {
+
+    public MutableLiveData<Integer> seekbarValue = new MutableLiveData<>();
+}
diff --git a/samples-flatfoot/codelabs/lifecycle/app/src/main/java/com/example/android/lifecycles/step5_solution/Activity_step5.java b/samples-flatfoot/codelabs/lifecycle/app/src/main/java/com/example/android/lifecycles/step5_solution/Activity_step5.java
new file mode 100644
index 0000000..89fe54b
--- /dev/null
+++ b/samples-flatfoot/codelabs/lifecycle/app/src/main/java/com/example/android/lifecycles/step5_solution/Activity_step5.java
@@ -0,0 +1,35 @@
+/*
+ * Copyright 2017, The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES 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.lifecycles.step5_solution;
+
+import android.os.Bundle;
+
+import android.arch.lifecycle.LifecycleActivity;
+import com.example.android.codelabs.lifecycle.R;
+
+/**
+ * Shows two {@link Fragment_step5} fragments.
+ */
+public class Activity_step5 extends LifecycleActivity {
+
+    @Override
+    protected void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+
+        setContentView(R.layout.activity_step5_solution);
+    }
+}
diff --git a/samples-flatfoot/codelabs/lifecycle/app/src/main/java/com/example/android/lifecycles/step5_solution/Fragment_step5.java b/samples-flatfoot/codelabs/lifecycle/app/src/main/java/com/example/android/lifecycles/step5_solution/Fragment_step5.java
new file mode 100644
index 0000000..f82eb87
--- /dev/null
+++ b/samples-flatfoot/codelabs/lifecycle/app/src/main/java/com/example/android/lifecycles/step5_solution/Fragment_step5.java
@@ -0,0 +1,89 @@
+/*
+ * Copyright 2017, The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES 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.lifecycles.step5_solution;
+
+
+import android.arch.lifecycle.LifecycleOwner;
+import android.arch.lifecycle.Observer;
+import android.arch.lifecycle.ViewModelProviders;
+import android.os.Bundle;
+import android.support.annotation.Nullable;
+import android.support.v4.app.Fragment;
+import android.util.Log;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.SeekBar;
+
+import com.example.android.codelabs.lifecycle.R;
+import com.example.android.lifecycles.step5.SeekBarViewModel;
+
+/**
+ * Shows a SeekBar that is synced with a value in a ViewModel.
+ */
+public class Fragment_step5 extends Fragment {
+
+    private SeekBar mSeekBar;
+
+    private SeekBarViewModel mSeekBarViewModel;
+
+    @Override
+    public View onCreateView(LayoutInflater inflater, ViewGroup container,
+                             Bundle savedInstanceState) {
+        // Inflate the layout for this fragment
+        View root = inflater.inflate(R.layout.fragment_step5, container, false);
+        mSeekBar = (SeekBar) root.findViewById(R.id.seekBar);
+
+        mSeekBarViewModel = ViewModelProviders.of(getActivity()).get(SeekBarViewModel.class);
+
+        subscribeSeekBar();
+
+        return root;
+    }
+
+    private void subscribeSeekBar() {
+
+        // Update the ViewModel when the SeekBar is changed.
+        mSeekBar.setOnSeekBarChangeListener(new SeekBar.OnSeekBarChangeListener() {
+            @Override
+            public void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser) {
+                if (fromUser) {
+                    Log.d("Step5", "Progress changed!");
+                    mSeekBarViewModel.seekbarValue.setValue(progress);
+                }
+            }
+
+            @Override
+            public void onStartTrackingTouch(SeekBar seekBar) { }
+
+            @Override
+            public void onStopTrackingTouch(SeekBar seekBar) { }
+        });
+
+        // Update the SeekBar when the ViewModel is changed.
+        mSeekBarViewModel.seekbarValue.observe(
+                (LifecycleOwner) getActivity(),
+                new Observer<Integer>() {
+            @Override
+            public void onChanged(@Nullable Integer value) {
+                if (value != null) {
+                    mSeekBar.setProgress(value);
+                }
+            }
+        });
+    }
+}
diff --git a/samples-flatfoot/codelabs/lifecycle/app/src/main/res/layout/activity_main.xml b/samples-flatfoot/codelabs/lifecycle/app/src/main/res/layout/activity_main.xml
new file mode 100644
index 0000000..b48e95e
--- /dev/null
+++ b/samples-flatfoot/codelabs/lifecycle/app/src/main/res/layout/activity_main.xml
@@ -0,0 +1,44 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  ~ Copyright 2017, The Android Open Source Project
+  ~
+  ~ Licensed under the Apache License, Version 2.0 (the "License");
+  ~ you may not use this file except in compliance with the License.
+  ~ You may obtain a copy of the License at
+  ~
+  ~      http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing, software
+  ~ distributed under the License is distributed on an "AS IS" BASIS,
+  ~ WITHOUT 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:id="@+id/activity_main"
+    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="com.example.android.lifecycles.step1.ChronoActivity1">
+
+    <TextView
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:text="Hello World!"
+        android:layout_centerVertical="true"
+        android:layout_centerHorizontal="true"
+        android:id="@+id/hello_textview"/>
+
+    <Chronometer
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:layout_above="@+id/hello_textview"
+        android:layout_centerHorizontal="true"
+        android:id="@+id/chronometer"/>
+</RelativeLayout>
diff --git a/samples-flatfoot/codelabs/lifecycle/app/src/main/res/layout/activity_step5.xml b/samples-flatfoot/codelabs/lifecycle/app/src/main/res/layout/activity_step5.xml
new file mode 100644
index 0000000..479cffd
--- /dev/null
+++ b/samples-flatfoot/codelabs/lifecycle/app/src/main/res/layout/activity_step5.xml
@@ -0,0 +1,42 @@
+<?xml version="1.0" encoding="utf-8"?><!--
+  ~ Copyright 2017, The Android Open Source Project
+  ~
+  ~ Licensed under the Apache License, Version 2.0 (the "License");
+  ~ you may not use this file except in compliance with the License.
+  ~ You may obtain a copy of the License at
+  ~
+  ~      http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing, software
+  ~ distributed under the License is distributed on an "AS IS" BASIS,
+  ~ WITHOUT 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:id="@+id/activity_main"
+    android:layout_width="match_parent"
+    android:layout_height="match_parent"
+    android:orientation="vertical"
+    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="com.example.android.lifecycles.step5.Activity_step5">
+
+    <fragment
+        android:id="@+id/fragment1"
+        android:name="com.example.android.lifecycles.step5.Fragment_step5"
+        android:layout_width="match_parent"
+        android:layout_height="0dp"
+        android:layout_weight="1" />
+
+    <fragment
+        android:id="@+id/fragment2"
+        android:name="com.example.android.lifecycles.step5.Fragment_step5"
+        android:layout_width="match_parent"
+        android:layout_height="0dp"
+        android:layout_weight="1" />
+</LinearLayout>
diff --git a/samples-flatfoot/codelabs/lifecycle/app/src/main/res/layout/activity_step5_solution.xml b/samples-flatfoot/codelabs/lifecycle/app/src/main/res/layout/activity_step5_solution.xml
new file mode 100644
index 0000000..a307499
--- /dev/null
+++ b/samples-flatfoot/codelabs/lifecycle/app/src/main/res/layout/activity_step5_solution.xml
@@ -0,0 +1,42 @@
+<?xml version="1.0" encoding="utf-8"?><!--
+  ~ Copyright 2017, The Android Open Source Project
+  ~
+  ~ Licensed under the Apache License, Version 2.0 (the "License");
+  ~ you may not use this file except in compliance with the License.
+  ~ You may obtain a copy of the License at
+  ~
+  ~      http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing, software
+  ~ distributed under the License is distributed on an "AS IS" BASIS,
+  ~ WITHOUT 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:id="@+id/activity_main"
+    android:layout_width="match_parent"
+    android:layout_height="match_parent"
+    android:orientation="vertical"
+    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="com.example.android.lifecycles.step5_solution.Activity_step5">
+
+    <fragment
+        android:id="@+id/fragment1"
+        android:name="com.example.android.lifecycles.step5_solution.Fragment_step5"
+        android:layout_width="match_parent"
+        android:layout_height="0dp"
+        android:layout_weight="1" />
+
+    <fragment
+        android:id="@+id/fragment2"
+        android:name="com.example.android.lifecycles.step5_solution.Fragment_step5"
+        android:layout_width="match_parent"
+        android:layout_height="0dp"
+        android:layout_weight="1" />
+</LinearLayout>
diff --git a/samples-flatfoot/codelabs/lifecycle/app/src/main/res/layout/chrono_activity_3.xml b/samples-flatfoot/codelabs/lifecycle/app/src/main/res/layout/chrono_activity_3.xml
new file mode 100644
index 0000000..cdbaa6f
--- /dev/null
+++ b/samples-flatfoot/codelabs/lifecycle/app/src/main/res/layout/chrono_activity_3.xml
@@ -0,0 +1,45 @@
+<?xml version="1.0" encoding="utf-8"?><!--
+  ~ Copyright 2017, The Android Open Source Project
+  ~
+  ~ Licensed under the Apache License, Version 2.0 (the "License");
+  ~ you may not use this file except in compliance with the License.
+  ~ You may obtain a copy of the License at
+  ~
+  ~      http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing, software
+  ~ distributed under the License is distributed on an "AS IS" BASIS,
+  ~ WITHOUT 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:id="@+id/activity_main"
+    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="com.example.android.persistence.codelab.step_databinding.ChronoDataBindingActivity">
+
+    <TextView
+        android:id="@+id/hello_textview"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:layout_centerHorizontal="true"
+        android:layout_centerVertical="true"
+        android:text="Hello World!" />
+
+    <TextView
+        android:id="@+id/timer_textview"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:layout_below="@id/hello_textview"
+        android:layout_centerHorizontal="true"
+        android:layout_centerVertical="true" />
+
+</RelativeLayout>
diff --git a/samples-flatfoot/codelabs/lifecycle/app/src/main/res/layout/chrono_activity_databinding.xml b/samples-flatfoot/codelabs/lifecycle/app/src/main/res/layout/chrono_activity_databinding.xml
new file mode 100644
index 0000000..6d2698e
--- /dev/null
+++ b/samples-flatfoot/codelabs/lifecycle/app/src/main/res/layout/chrono_activity_databinding.xml
@@ -0,0 +1,59 @@
+<?xml version="1.0" encoding="utf-8"?><!--
+  ~ Copyright 2017, The Android Open Source Project
+  ~
+  ~ Licensed under the Apache License, Version 2.0 (the "License");
+  ~ you may not use this file except in compliance with the License.
+  ~ You may obtain a copy of the License at
+  ~
+  ~      http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing, software
+  ~ distributed under the License is distributed on an "AS IS" BASIS,
+  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  ~ See the License for the specific language governing permissions and
+  ~ limitations under the License.
+  -->
+
+<layout>
+
+    <data>
+
+        <import type="android.databinding.ObservableField" />
+
+        <variable
+            name="elapsedTime"
+            type="ObservableField&lt;Long&gt;">
+
+        </variable>
+    </data>
+
+    <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
+        xmlns:tools="http://schemas.android.com/tools"
+        android:id="@+id/activity_main"
+        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="com.example.android.persistence.codelab.step_databinding.ChronoDataBindingActivity">
+
+        <TextView
+            android:id="@+id/hello_textview"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:layout_centerHorizontal="true"
+            android:layout_centerVertical="true"
+            android:text="Hello World!" />
+
+        <TextView
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:layout_below="@id/hello_textview"
+            android:layout_centerHorizontal="true"
+            android:layout_centerVertical="true"
+            android:text="@{String.format(@string/seconds, elapsedTime)}" />
+
+    </RelativeLayout>
+
+</layout>
\ No newline at end of file
diff --git a/samples-flatfoot/codelabs/lifecycle/app/src/main/res/layout/fragment_step5.xml b/samples-flatfoot/codelabs/lifecycle/app/src/main/res/layout/fragment_step5.xml
new file mode 100644
index 0000000..3821796
--- /dev/null
+++ b/samples-flatfoot/codelabs/lifecycle/app/src/main/res/layout/fragment_step5.xml
@@ -0,0 +1,28 @@
+<!--
+  ~ Copyright 2017, The Android Open Source Project
+  ~
+  ~ Licensed under the Apache License, Version 2.0 (the "License");
+  ~ you may not use this file except in compliance with the License.
+  ~ You may obtain a copy of the License at
+  ~
+  ~      http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing, software
+  ~ distributed under the License is distributed on an "AS IS" BASIS,
+  ~ WITHOUT 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:layout_width="match_parent"
+    android:layout_height="match_parent"
+    tools:context="com.example.android.lifecycles.step5_solution.Fragment_step5">
+
+    <SeekBar
+        android:id="@+id/seekBar"
+        android:layout_width="match_parent"
+        android:layout_height="match_parent" />
+
+</FrameLayout>
diff --git a/samples-flatfoot/codelabs/lifecycle/app/src/main/res/layout/location_activity.xml b/samples-flatfoot/codelabs/lifecycle/app/src/main/res/layout/location_activity.xml
new file mode 100644
index 0000000..f1bc410
--- /dev/null
+++ b/samples-flatfoot/codelabs/lifecycle/app/src/main/res/layout/location_activity.xml
@@ -0,0 +1,28 @@
+<?xml version="1.0" encoding="utf-8"?><!--
+  ~ Copyright 2017, The Android Open Source Project
+  ~
+  ~ Licensed under the Apache License, Version 2.0 (the "License");
+  ~ you may not use this file except in compliance with the License.
+  ~ You may obtain a copy of the License at
+  ~
+  ~      http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing, software
+  ~ distributed under the License is distributed on an "AS IS" BASIS,
+  ~ WITHOUT 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">
+
+    <TextView
+        android:id="@+id/location"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:layout_margin="12dp"
+        android:text="@string/location" />
+</LinearLayout>
\ No newline at end of file
diff --git a/samples-flatfoot/codelabs/lifecycle/app/src/main/res/mipmap-hdpi/ic_launcher.png b/samples-flatfoot/codelabs/lifecycle/app/src/main/res/mipmap-hdpi/ic_launcher.png
new file mode 100644
index 0000000..e19d44f
--- /dev/null
+++ b/samples-flatfoot/codelabs/lifecycle/app/src/main/res/mipmap-hdpi/ic_launcher.png
Binary files differ
diff --git a/samples-flatfoot/codelabs/lifecycle/app/src/main/res/mipmap-mdpi/ic_launcher.png b/samples-flatfoot/codelabs/lifecycle/app/src/main/res/mipmap-mdpi/ic_launcher.png
new file mode 100644
index 0000000..7876250
--- /dev/null
+++ b/samples-flatfoot/codelabs/lifecycle/app/src/main/res/mipmap-mdpi/ic_launcher.png
Binary files differ
diff --git a/samples-flatfoot/codelabs/lifecycle/app/src/main/res/mipmap-xhdpi/ic_launcher.png b/samples-flatfoot/codelabs/lifecycle/app/src/main/res/mipmap-xhdpi/ic_launcher.png
new file mode 100644
index 0000000..9ae3725
--- /dev/null
+++ b/samples-flatfoot/codelabs/lifecycle/app/src/main/res/mipmap-xhdpi/ic_launcher.png
Binary files differ
diff --git a/samples-flatfoot/codelabs/lifecycle/app/src/main/res/mipmap-xxhdpi/ic_launcher.png b/samples-flatfoot/codelabs/lifecycle/app/src/main/res/mipmap-xxhdpi/ic_launcher.png
new file mode 100644
index 0000000..6a6c3aa
--- /dev/null
+++ b/samples-flatfoot/codelabs/lifecycle/app/src/main/res/mipmap-xxhdpi/ic_launcher.png
Binary files differ
diff --git a/samples-flatfoot/codelabs/lifecycle/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png b/samples-flatfoot/codelabs/lifecycle/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png
new file mode 100644
index 0000000..43ca523
--- /dev/null
+++ b/samples-flatfoot/codelabs/lifecycle/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png
Binary files differ
diff --git a/samples-flatfoot/codelabs/lifecycle/app/src/main/res/values/colors.xml b/samples-flatfoot/codelabs/lifecycle/app/src/main/res/values/colors.xml
new file mode 100644
index 0000000..255f15d
--- /dev/null
+++ b/samples-flatfoot/codelabs/lifecycle/app/src/main/res/values/colors.xml
@@ -0,0 +1,22 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  ~ Copyright 2017, The Android Open Source Project
+  ~
+  ~ Licensed under the Apache License, Version 2.0 (the "License");
+  ~ you may not use this file except in compliance with the License.
+  ~ You may obtain a copy of the License at
+  ~
+  ~      http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing, software
+  ~ distributed under the License is distributed on an "AS IS" BASIS,
+  ~ WITHOUT 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>
+    <color name="colorPrimary">#3F51B5</color>
+    <color name="colorPrimaryDark">#303F9F</color>
+    <color name="colorAccent">#FF4081</color>
+</resources>
diff --git a/samples-flatfoot/codelabs/lifecycle/app/src/main/res/values/dimens.xml b/samples-flatfoot/codelabs/lifecycle/app/src/main/res/values/dimens.xml
new file mode 100644
index 0000000..9a3477e
--- /dev/null
+++ b/samples-flatfoot/codelabs/lifecycle/app/src/main/res/values/dimens.xml
@@ -0,0 +1,21 @@
+<!--
+  ~ Copyright 2017, The Android Open Source Project
+  ~
+  ~ Licensed under the Apache License, Version 2.0 (the "License");
+  ~ you may not use this file except in compliance with the License.
+  ~ You may obtain a copy of the License at
+  ~
+  ~      http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing, software
+  ~ distributed under the License is distributed on an "AS IS" BASIS,
+  ~ WITHOUT 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>
diff --git a/samples-flatfoot/codelabs/lifecycle/app/src/main/res/values/strings.xml b/samples-flatfoot/codelabs/lifecycle/app/src/main/res/values/strings.xml
new file mode 100644
index 0000000..198dac9
--- /dev/null
+++ b/samples-flatfoot/codelabs/lifecycle/app/src/main/res/values/strings.xml
@@ -0,0 +1,21 @@
+<!--
+  ~ Copyright 2017, The Android Open Source Project
+  ~
+  ~ Licensed under the Apache License, Version 2.0 (the "License");
+  ~ you may not use this file except in compliance with the License.
+  ~ You may obtain a copy of the License at
+  ~
+  ~      http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing, software
+  ~ distributed under the License is distributed on an "AS IS" BASIS,
+  ~ WITHOUT 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">Lifecycle codelab</string>
+    <string name="seconds">%d seconds elapsed</string>
+    <string name="location">Location</string>
+</resources>
diff --git a/samples-flatfoot/codelabs/lifecycle/app/src/main/res/values/styles.xml b/samples-flatfoot/codelabs/lifecycle/app/src/main/res/values/styles.xml
new file mode 100644
index 0000000..7aa7228
--- /dev/null
+++ b/samples-flatfoot/codelabs/lifecycle/app/src/main/res/values/styles.xml
@@ -0,0 +1,27 @@
+<!--
+  ~ Copyright 2017, The Android Open Source Project
+  ~
+  ~ Licensed under the Apache License, Version 2.0 (the "License");
+  ~ you may not use this file except in compliance with the License.
+  ~ You may obtain a copy of the License at
+  ~
+  ~      http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing, software
+  ~ distributed under the License is distributed on an "AS IS" BASIS,
+  ~ WITHOUT 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. -->
+    <style name="AppTheme" parent="Theme.AppCompat.Light.DarkActionBar">
+        <!-- Customize your theme here. -->
+        <item name="colorPrimary">@color/colorPrimary</item>
+        <item name="colorPrimaryDark">@color/colorPrimaryDark</item>
+        <item name="colorAccent">@color/colorAccent</item>
+    </style>
+
+</resources>
diff --git a/samples-flatfoot/codelabs/lifecycle/build.gradle b/samples-flatfoot/codelabs/lifecycle/build.gradle
new file mode 100644
index 0000000..c68afa8
--- /dev/null
+++ b/samples-flatfoot/codelabs/lifecycle/build.gradle
@@ -0,0 +1,52 @@
+/*
+ * Copyright 2017, The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT 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 build file where you can add configuration options common to all sub-projects/modules.
+
+buildscript {
+    repositories {
+        jcenter()
+    }
+    dependencies {
+        classpath 'com.android.tools.build:gradle:2.3.0'
+
+        // NOTE: Do not place your application dependencies here; they belong
+        // in the individual module build.gradle files
+    }
+}
+
+allprojects {
+    repositories {
+        jcenter()
+        maven {
+            url "../../../prebuilts0905"
+        }
+    }
+}
+
+task clean(type: Delete) {
+    delete rootProject.buildDir
+}
+
+ext {
+    buildToolsVersion = "25.0.2"
+    supportLibVersion = "25.3.1"
+    runnerVersion = "0.5"
+    rulesVersion = "0.5"
+    espressoVersion = "2.2.2"
+    archLifecycleVersion = "1.0-SNAPSHOT"
+    archRoomVersion = "1.0-SNAPSHOT"
+}
\ No newline at end of file
diff --git a/samples-flatfoot/codelabs/lifecycle/gradle.properties b/samples-flatfoot/codelabs/lifecycle/gradle.properties
new file mode 100644
index 0000000..684bee6
--- /dev/null
+++ b/samples-flatfoot/codelabs/lifecycle/gradle.properties
@@ -0,0 +1,33 @@
+#
+# Copyright 2017, The Android Open Source Project
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#      http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+
+# Project-wide Gradle settings.
+
+# IDE (e.g. Android Studio) users:
+# Gradle settings configured through the IDE *will override*
+# any settings specified in this file.
+
+# For more details on how to configure your build environment visit
+# http://www.gradle.org/docs/current/userguide/build_environment.html
+
+# Specifies the JVM arguments used for the daemon process.
+# The setting is particularly useful for tweaking memory settings.
+org.gradle.jvmargs=-Xmx1536m
+
+# When configured, Gradle will run in incubating parallel mode.
+# This option should only be used with decoupled projects. More details, visit
+# http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects
+# org.gradle.parallel=true
diff --git a/samples-flatfoot/codelabs/lifecycle/gradle/wrapper/gradle-wrapper.jar b/samples-flatfoot/codelabs/lifecycle/gradle/wrapper/gradle-wrapper.jar
new file mode 100644
index 0000000..13372ae
--- /dev/null
+++ b/samples-flatfoot/codelabs/lifecycle/gradle/wrapper/gradle-wrapper.jar
Binary files differ
diff --git a/samples-flatfoot/codelabs/lifecycle/gradle/wrapper/gradle-wrapper.properties b/samples-flatfoot/codelabs/lifecycle/gradle/wrapper/gradle-wrapper.properties
new file mode 100644
index 0000000..1af3d91
--- /dev/null
+++ b/samples-flatfoot/codelabs/lifecycle/gradle/wrapper/gradle-wrapper.properties
@@ -0,0 +1,22 @@
+#
+# Copyright 2017, The Android Open Source Project
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#      http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+
+#Mon Feb 20 17:11:12 GMT 2017
+distributionBase=GRADLE_USER_HOME
+distributionPath=wrapper/dists
+zipStoreBase=GRADLE_USER_HOME
+zipStorePath=wrapper/dists
+distributionUrl=https\://services.gradle.org/distributions/gradle-3.3-all.zip
diff --git a/samples-flatfoot/codelabs/lifecycle/gradlew b/samples-flatfoot/codelabs/lifecycle/gradlew
new file mode 100755
index 0000000..9d82f78
--- /dev/null
+++ b/samples-flatfoot/codelabs/lifecycle/gradlew
@@ -0,0 +1,160 @@
+#!/usr/bin/env bash
+
+##############################################################################
+##
+##  Gradle start up script for UN*X
+##
+##############################################################################
+
+# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
+DEFAULT_JVM_OPTS=""
+
+APP_NAME="Gradle"
+APP_BASE_NAME=`basename "$0"`
+
+# Use the maximum available, or set MAX_FD != -1 to use that value.
+MAX_FD="maximum"
+
+warn ( ) {
+    echo "$*"
+}
+
+die ( ) {
+    echo
+    echo "$*"
+    echo
+    exit 1
+}
+
+# OS specific support (must be 'true' or 'false').
+cygwin=false
+msys=false
+darwin=false
+case "`uname`" in
+  CYGWIN* )
+    cygwin=true
+    ;;
+  Darwin* )
+    darwin=true
+    ;;
+  MINGW* )
+    msys=true
+    ;;
+esac
+
+# Attempt to set APP_HOME
+# Resolve links: $0 may be a link
+PRG="$0"
+# Need this for relative symlinks.
+while [ -h "$PRG" ] ; do
+    ls=`ls -ld "$PRG"`
+    link=`expr "$ls" : '.*-> \(.*\)$'`
+    if expr "$link" : '/.*' > /dev/null; then
+        PRG="$link"
+    else
+        PRG=`dirname "$PRG"`"/$link"
+    fi
+done
+SAVED="`pwd`"
+cd "`dirname \"$PRG\"`/" >/dev/null
+APP_HOME="`pwd -P`"
+cd "$SAVED" >/dev/null
+
+CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
+
+# Determine the Java command to use to start the JVM.
+if [ -n "$JAVA_HOME" ] ; then
+    if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
+        # IBM's JDK on AIX uses strange locations for the executables
+        JAVACMD="$JAVA_HOME/jre/sh/java"
+    else
+        JAVACMD="$JAVA_HOME/bin/java"
+    fi
+    if [ ! -x "$JAVACMD" ] ; then
+        die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
+
+Please set the JAVA_HOME variable in your environment to match the
+location of your Java installation."
+    fi
+else
+    JAVACMD="java"
+    which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
+
+Please set the JAVA_HOME variable in your environment to match the
+location of your Java installation."
+fi
+
+# Increase the maximum file descriptors if we can.
+if [ "$cygwin" = "false" -a "$darwin" = "false" ] ; then
+    MAX_FD_LIMIT=`ulimit -H -n`
+    if [ $? -eq 0 ] ; then
+        if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then
+            MAX_FD="$MAX_FD_LIMIT"
+        fi
+        ulimit -n $MAX_FD
+        if [ $? -ne 0 ] ; then
+            warn "Could not set maximum file descriptor limit: $MAX_FD"
+        fi
+    else
+        warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT"
+    fi
+fi
+
+# For Darwin, add options to specify how the application appears in the dock
+if $darwin; then
+    GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\""
+fi
+
+# For Cygwin, switch paths to Windows format before running java
+if $cygwin ; then
+    APP_HOME=`cygpath --path --mixed "$APP_HOME"`
+    CLASSPATH=`cygpath --path --mixed "$CLASSPATH"`
+    JAVACMD=`cygpath --unix "$JAVACMD"`
+
+    # We build the pattern for arguments to be converted via cygpath
+    ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null`
+    SEP=""
+    for dir in $ROOTDIRSRAW ; do
+        ROOTDIRS="$ROOTDIRS$SEP$dir"
+        SEP="|"
+    done
+    OURCYGPATTERN="(^($ROOTDIRS))"
+    # Add a user-defined pattern to the cygpath arguments
+    if [ "$GRADLE_CYGPATTERN" != "" ] ; then
+        OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)"
+    fi
+    # Now convert the arguments - kludge to limit ourselves to /bin/sh
+    i=0
+    for arg in "$@" ; do
+        CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -`
+        CHECK2=`echo "$arg"|egrep -c "^-"`                                 ### Determine if an option
+
+        if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then                    ### Added a condition
+            eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"`
+        else
+            eval `echo args$i`="\"$arg\""
+        fi
+        i=$((i+1))
+    done
+    case $i in
+        (0) set -- ;;
+        (1) set -- "$args0" ;;
+        (2) set -- "$args0" "$args1" ;;
+        (3) set -- "$args0" "$args1" "$args2" ;;
+        (4) set -- "$args0" "$args1" "$args2" "$args3" ;;
+        (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;;
+        (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;;
+        (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;;
+        (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;;
+        (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;;
+    esac
+fi
+
+# Split up the JVM_OPTS And GRADLE_OPTS values into an array, following the shell quoting and substitution rules
+function splitJvmOpts() {
+    JVM_OPTS=("$@")
+}
+eval splitJvmOpts $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS
+JVM_OPTS[${#JVM_OPTS[*]}]="-Dorg.gradle.appname=$APP_BASE_NAME"
+
+exec "$JAVACMD" "${JVM_OPTS[@]}" -classpath "$CLASSPATH" org.gradle.wrapper.GradleWrapperMain "$@"
diff --git a/samples-flatfoot/codelabs/lifecycle/gradlew.bat b/samples-flatfoot/codelabs/lifecycle/gradlew.bat
new file mode 100644
index 0000000..aec9973
--- /dev/null
+++ b/samples-flatfoot/codelabs/lifecycle/gradlew.bat
@@ -0,0 +1,90 @@
+@if "%DEBUG%" == "" @echo off

+@rem ##########################################################################

+@rem

+@rem  Gradle startup script for Windows

+@rem

+@rem ##########################################################################

+

+@rem Set local scope for the variables with windows NT shell

+if "%OS%"=="Windows_NT" setlocal

+

+@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.

+set DEFAULT_JVM_OPTS=

+

+set DIRNAME=%~dp0

+if "%DIRNAME%" == "" set DIRNAME=.

+set APP_BASE_NAME=%~n0

+set APP_HOME=%DIRNAME%

+

+@rem Find java.exe

+if defined JAVA_HOME goto findJavaFromJavaHome

+

+set JAVA_EXE=java.exe

+%JAVA_EXE% -version >NUL 2>&1

+if "%ERRORLEVEL%" == "0" goto init

+

+echo.

+echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.

+echo.

+echo Please set the JAVA_HOME variable in your environment to match the

+echo location of your Java installation.

+

+goto fail

+

+:findJavaFromJavaHome

+set JAVA_HOME=%JAVA_HOME:"=%

+set JAVA_EXE=%JAVA_HOME%/bin/java.exe

+

+if exist "%JAVA_EXE%" goto init

+

+echo.

+echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%

+echo.

+echo Please set the JAVA_HOME variable in your environment to match the

+echo location of your Java installation.

+

+goto fail

+

+:init

+@rem Get command-line arguments, handling Windowz variants

+

+if not "%OS%" == "Windows_NT" goto win9xME_args

+if "%@eval[2+2]" == "4" goto 4NT_args

+

+:win9xME_args

+@rem Slurp the command line arguments.

+set CMD_LINE_ARGS=

+set _SKIP=2

+

+:win9xME_args_slurp

+if "x%~1" == "x" goto execute

+

+set CMD_LINE_ARGS=%*

+goto execute

+

+:4NT_args

+@rem Get arguments from the 4NT Shell from JP Software

+set CMD_LINE_ARGS=%$

+

+:execute

+@rem Setup the command line

+

+set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar

+

+@rem Execute Gradle

+"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS%

+

+:end

+@rem End local scope for the variables with windows NT shell

+if "%ERRORLEVEL%"=="0" goto mainEnd

+

+:fail

+rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of

+rem the _cmd.exe /c_ return code!

+if  not "" == "%GRADLE_EXIT_CONSOLE%" exit 1

+exit /b 1

+

+:mainEnd

+if "%OS%"=="Windows_NT" endlocal

+

+:omega

diff --git a/samples-flatfoot/codelabs/lifecycle/settings.gradle b/samples-flatfoot/codelabs/lifecycle/settings.gradle
new file mode 100644
index 0000000..a266f7d
--- /dev/null
+++ b/samples-flatfoot/codelabs/lifecycle/settings.gradle
@@ -0,0 +1,17 @@
+/*
+ * Copyright 2017, The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+include ':app'
diff --git a/samples-flatfoot/codelabs/persistence/.gitignore b/samples-flatfoot/codelabs/persistence/.gitignore
new file mode 100644
index 0000000..c33eb5b
--- /dev/null
+++ b/samples-flatfoot/codelabs/persistence/.gitignore
@@ -0,0 +1,9 @@
+*.iml
+.gradle
+/local.properties
+/.idea/workspace.xml
+/.idea/libraries
+.DS_Store
+build/
+/captures
+.externalNativeBuild
diff --git a/samples-flatfoot/codelabs/persistence/.idea/runConfigurations/Step_1___Dao.xml b/samples-flatfoot/codelabs/persistence/.idea/runConfigurations/Step_1___Dao.xml
new file mode 100644
index 0000000..c4fb918
--- /dev/null
+++ b/samples-flatfoot/codelabs/persistence/.idea/runConfigurations/Step_1___Dao.xml
@@ -0,0 +1,51 @@
+<component name="ProjectRunConfigurationManager">
+  <configuration default="false" name="Step 1 - Dao" type="AndroidRunConfigurationType" factoryName="Android App" activateToolWindowBeforeRun="false">
+    <module name="app" />
+    <option name="DEPLOY" value="true" />
+    <option name="ARTIFACT_NAME" value="" />
+    <option name="PM_INSTALL_OPTIONS" value="" />
+    <option name="ACTIVITY_EXTRA_FLAGS" value="" />
+    <option name="MODE" value="specific_activity" />
+    <option name="PREFERRED_AVD" value="" />
+    <option name="CLEAR_LOGCAT" value="false" />
+    <option name="SHOW_LOGCAT_AUTOMATICALLY" value="false" />
+    <option name="SKIP_NOOP_APK_INSTALLATIONS" value="true" />
+    <option name="FORCE_STOP_RUNNING_APP" value="true" />
+    <option name="TARGET_SELECTION_MODE" value="SHOW_DIALOG" />
+    <option name="USE_LAST_SELECTED_DEVICE" value="false" />
+    <option name="PREFERRED_AVD" value="" />
+    <option name="SELECTED_CLOUD_MATRIX_CONFIGURATION_ID" value="-1" />
+    <option name="SELECTED_CLOUD_MATRIX_PROJECT_ID" value="" />
+    <option name="DEBUGGER_TYPE" value="Auto" />
+    <Auto>
+      <option name="USE_JAVA_AWARE_DEBUGGER" value="false" />
+      <option name="SHOW_STATIC_VARS" value="true" />
+      <option name="WORKING_DIR" value="" />
+      <option name="TARGET_LOGGING_CHANNELS" value="lldb process:gdb-remote packets" />
+      <option name="SHOW_OPTIMIZED_WARNING" value="true" />
+    </Auto>
+    <Hybrid>
+      <option name="USE_JAVA_AWARE_DEBUGGER" value="false" />
+      <option name="SHOW_STATIC_VARS" value="true" />
+      <option name="WORKING_DIR" value="" />
+      <option name="TARGET_LOGGING_CHANNELS" value="lldb process:gdb-remote packets" />
+      <option name="SHOW_OPTIMIZED_WARNING" value="true" />
+    </Hybrid>
+    <Java />
+    <Native>
+      <option name="USE_JAVA_AWARE_DEBUGGER" value="false" />
+      <option name="SHOW_STATIC_VARS" value="true" />
+      <option name="WORKING_DIR" value="" />
+      <option name="TARGET_LOGGING_CHANNELS" value="lldb process:gdb-remote packets" />
+      <option name="SHOW_OPTIMIZED_WARNING" value="true" />
+    </Native>
+    <Profilers>
+      <option name="ENABLE_ADVANCED_PROFILING" value="true" />
+      <option name="SUPPORT_LIB_ENABLED" value="true" />
+      <option name="INSTRUMENTATION_ENABLED" value="true" />
+    </Profilers>
+    <option name="DEEP_LINK" value="" />
+    <option name="ACTIVITY_CLASS" value="com.example.android.persistence.codelab.step1.UsersActivity" />
+    <method />
+  </configuration>
+</component>
\ No newline at end of file
diff --git a/samples-flatfoot/codelabs/persistence/.idea/runConfigurations/Step_1___Solution.xml b/samples-flatfoot/codelabs/persistence/.idea/runConfigurations/Step_1___Solution.xml
new file mode 100644
index 0000000..b047801
--- /dev/null
+++ b/samples-flatfoot/codelabs/persistence/.idea/runConfigurations/Step_1___Solution.xml
@@ -0,0 +1,51 @@
+<component name="ProjectRunConfigurationManager">
+  <configuration default="false" name="Step 1 - Solution" type="AndroidRunConfigurationType" factoryName="Android App" activateToolWindowBeforeRun="false">
+    <module name="app" />
+    <option name="DEPLOY" value="true" />
+    <option name="ARTIFACT_NAME" value="" />
+    <option name="PM_INSTALL_OPTIONS" value="" />
+    <option name="ACTIVITY_EXTRA_FLAGS" value="" />
+    <option name="MODE" value="specific_activity" />
+    <option name="PREFERRED_AVD" value="" />
+    <option name="CLEAR_LOGCAT" value="false" />
+    <option name="SHOW_LOGCAT_AUTOMATICALLY" value="false" />
+    <option name="SKIP_NOOP_APK_INSTALLATIONS" value="true" />
+    <option name="FORCE_STOP_RUNNING_APP" value="true" />
+    <option name="TARGET_SELECTION_MODE" value="SHOW_DIALOG" />
+    <option name="USE_LAST_SELECTED_DEVICE" value="true" />
+    <option name="PREFERRED_AVD" value="" />
+    <option name="SELECTED_CLOUD_MATRIX_CONFIGURATION_ID" value="-1" />
+    <option name="SELECTED_CLOUD_MATRIX_PROJECT_ID" value="" />
+    <option name="DEBUGGER_TYPE" value="Auto" />
+    <Auto>
+      <option name="USE_JAVA_AWARE_DEBUGGER" value="false" />
+      <option name="SHOW_STATIC_VARS" value="true" />
+      <option name="WORKING_DIR" value="" />
+      <option name="TARGET_LOGGING_CHANNELS" value="lldb process:gdb-remote packets" />
+      <option name="SHOW_OPTIMIZED_WARNING" value="true" />
+    </Auto>
+    <Hybrid>
+      <option name="USE_JAVA_AWARE_DEBUGGER" value="false" />
+      <option name="SHOW_STATIC_VARS" value="true" />
+      <option name="WORKING_DIR" value="" />
+      <option name="TARGET_LOGGING_CHANNELS" value="lldb process:gdb-remote packets" />
+      <option name="SHOW_OPTIMIZED_WARNING" value="true" />
+    </Hybrid>
+    <Java />
+    <Native>
+      <option name="USE_JAVA_AWARE_DEBUGGER" value="false" />
+      <option name="SHOW_STATIC_VARS" value="true" />
+      <option name="WORKING_DIR" value="" />
+      <option name="TARGET_LOGGING_CHANNELS" value="lldb process:gdb-remote packets" />
+      <option name="SHOW_OPTIMIZED_WARNING" value="true" />
+    </Native>
+    <Profilers>
+      <option name="ENABLE_ADVANCED_PROFILING" value="true" />
+      <option name="SUPPORT_LIB_ENABLED" value="true" />
+      <option name="INSTRUMENTATION_ENABLED" value="true" />
+    </Profilers>
+    <option name="DEEP_LINK" value="" />
+    <option name="ACTIVITY_CLASS" value="com.example.android.persistence.codelab.step1_solution.UsersActivity" />
+    <method />
+  </configuration>
+</component>
\ No newline at end of file
diff --git a/samples-flatfoot/codelabs/persistence/.idea/runConfigurations/Step_2___Relationships.xml b/samples-flatfoot/codelabs/persistence/.idea/runConfigurations/Step_2___Relationships.xml
new file mode 100644
index 0000000..17fe2f9
--- /dev/null
+++ b/samples-flatfoot/codelabs/persistence/.idea/runConfigurations/Step_2___Relationships.xml
@@ -0,0 +1,51 @@
+<component name="ProjectRunConfigurationManager">
+  <configuration default="false" name="Step 2 - Relationships" type="AndroidRunConfigurationType" factoryName="Android App" activateToolWindowBeforeRun="false">
+    <module name="app" />
+    <option name="DEPLOY" value="true" />
+    <option name="ARTIFACT_NAME" value="" />
+    <option name="PM_INSTALL_OPTIONS" value="" />
+    <option name="ACTIVITY_EXTRA_FLAGS" value="" />
+    <option name="MODE" value="specific_activity" />
+    <option name="PREFERRED_AVD" value="" />
+    <option name="CLEAR_LOGCAT" value="false" />
+    <option name="SHOW_LOGCAT_AUTOMATICALLY" value="false" />
+    <option name="SKIP_NOOP_APK_INSTALLATIONS" value="true" />
+    <option name="FORCE_STOP_RUNNING_APP" value="true" />
+    <option name="TARGET_SELECTION_MODE" value="SHOW_DIALOG" />
+    <option name="USE_LAST_SELECTED_DEVICE" value="true" />
+    <option name="PREFERRED_AVD" value="" />
+    <option name="SELECTED_CLOUD_MATRIX_CONFIGURATION_ID" value="-1" />
+    <option name="SELECTED_CLOUD_MATRIX_PROJECT_ID" value="" />
+    <option name="DEBUGGER_TYPE" value="Auto" />
+    <Auto>
+      <option name="USE_JAVA_AWARE_DEBUGGER" value="false" />
+      <option name="SHOW_STATIC_VARS" value="true" />
+      <option name="WORKING_DIR" value="" />
+      <option name="TARGET_LOGGING_CHANNELS" value="lldb process:gdb-remote packets" />
+      <option name="SHOW_OPTIMIZED_WARNING" value="true" />
+    </Auto>
+    <Hybrid>
+      <option name="USE_JAVA_AWARE_DEBUGGER" value="false" />
+      <option name="SHOW_STATIC_VARS" value="true" />
+      <option name="WORKING_DIR" value="" />
+      <option name="TARGET_LOGGING_CHANNELS" value="lldb process:gdb-remote packets" />
+      <option name="SHOW_OPTIMIZED_WARNING" value="true" />
+    </Hybrid>
+    <Java />
+    <Native>
+      <option name="USE_JAVA_AWARE_DEBUGGER" value="false" />
+      <option name="SHOW_STATIC_VARS" value="true" />
+      <option name="WORKING_DIR" value="" />
+      <option name="TARGET_LOGGING_CHANNELS" value="lldb process:gdb-remote packets" />
+      <option name="SHOW_OPTIMIZED_WARNING" value="true" />
+    </Native>
+    <Profilers>
+      <option name="ENABLE_ADVANCED_PROFILING" value="true" />
+      <option name="SUPPORT_LIB_ENABLED" value="true" />
+      <option name="INSTRUMENTATION_ENABLED" value="true" />
+    </Profilers>
+    <option name="DEEP_LINK" value="" />
+    <option name="ACTIVITY_CLASS" value="com.example.android.persistence.codelab.step2.JankShowUserActivity" />
+    <method />
+  </configuration>
+</component>
\ No newline at end of file
diff --git a/samples-flatfoot/codelabs/persistence/.idea/runConfigurations/Step_3___Async_calls.xml b/samples-flatfoot/codelabs/persistence/.idea/runConfigurations/Step_3___Async_calls.xml
new file mode 100644
index 0000000..28ddac1
--- /dev/null
+++ b/samples-flatfoot/codelabs/persistence/.idea/runConfigurations/Step_3___Async_calls.xml
@@ -0,0 +1,51 @@
+<component name="ProjectRunConfigurationManager">
+  <configuration default="false" name="Step 3 - Async calls" type="AndroidRunConfigurationType" factoryName="Android App" activateToolWindowBeforeRun="false">
+    <module name="app" />
+    <option name="DEPLOY" value="true" />
+    <option name="ARTIFACT_NAME" value="" />
+    <option name="PM_INSTALL_OPTIONS" value="" />
+    <option name="ACTIVITY_EXTRA_FLAGS" value="" />
+    <option name="MODE" value="specific_activity" />
+    <option name="PREFERRED_AVD" value="" />
+    <option name="CLEAR_LOGCAT" value="false" />
+    <option name="SHOW_LOGCAT_AUTOMATICALLY" value="false" />
+    <option name="SKIP_NOOP_APK_INSTALLATIONS" value="true" />
+    <option name="FORCE_STOP_RUNNING_APP" value="true" />
+    <option name="TARGET_SELECTION_MODE" value="SHOW_DIALOG" />
+    <option name="USE_LAST_SELECTED_DEVICE" value="true" />
+    <option name="PREFERRED_AVD" value="" />
+    <option name="SELECTED_CLOUD_MATRIX_CONFIGURATION_ID" value="-1" />
+    <option name="SELECTED_CLOUD_MATRIX_PROJECT_ID" value="" />
+    <option name="DEBUGGER_TYPE" value="Auto" />
+    <Auto>
+      <option name="USE_JAVA_AWARE_DEBUGGER" value="false" />
+      <option name="SHOW_STATIC_VARS" value="true" />
+      <option name="WORKING_DIR" value="" />
+      <option name="TARGET_LOGGING_CHANNELS" value="lldb process:gdb-remote packets" />
+      <option name="SHOW_OPTIMIZED_WARNING" value="true" />
+    </Auto>
+    <Hybrid>
+      <option name="USE_JAVA_AWARE_DEBUGGER" value="false" />
+      <option name="SHOW_STATIC_VARS" value="true" />
+      <option name="WORKING_DIR" value="" />
+      <option name="TARGET_LOGGING_CHANNELS" value="lldb process:gdb-remote packets" />
+      <option name="SHOW_OPTIMIZED_WARNING" value="true" />
+    </Hybrid>
+    <Java />
+    <Native>
+      <option name="USE_JAVA_AWARE_DEBUGGER" value="false" />
+      <option name="SHOW_STATIC_VARS" value="true" />
+      <option name="WORKING_DIR" value="" />
+      <option name="TARGET_LOGGING_CHANNELS" value="lldb process:gdb-remote packets" />
+      <option name="SHOW_OPTIMIZED_WARNING" value="true" />
+    </Native>
+    <Profilers>
+      <option name="ENABLE_ADVANCED_PROFILING" value="true" />
+      <option name="SUPPORT_LIB_ENABLED" value="true" />
+      <option name="INSTRUMENTATION_ENABLED" value="true" />
+    </Profilers>
+    <option name="DEEP_LINK" value="" />
+    <option name="ACTIVITY_CLASS" value="com.example.android.persistence.codelab.step3.BooksBorrowedByUserActivity" />
+    <method />
+  </configuration>
+</component>
\ No newline at end of file
diff --git a/samples-flatfoot/codelabs/persistence/.idea/runConfigurations/Step_3___Solution.xml b/samples-flatfoot/codelabs/persistence/.idea/runConfigurations/Step_3___Solution.xml
new file mode 100644
index 0000000..38fcfb3
--- /dev/null
+++ b/samples-flatfoot/codelabs/persistence/.idea/runConfigurations/Step_3___Solution.xml
@@ -0,0 +1,51 @@
+<component name="ProjectRunConfigurationManager">
+  <configuration default="false" name="Step 3 - Solution" type="AndroidRunConfigurationType" factoryName="Android App" activateToolWindowBeforeRun="false">
+    <module name="app" />
+    <option name="DEPLOY" value="true" />
+    <option name="ARTIFACT_NAME" value="" />
+    <option name="PM_INSTALL_OPTIONS" value="" />
+    <option name="ACTIVITY_EXTRA_FLAGS" value="" />
+    <option name="MODE" value="specific_activity" />
+    <option name="PREFERRED_AVD" value="" />
+    <option name="CLEAR_LOGCAT" value="false" />
+    <option name="SHOW_LOGCAT_AUTOMATICALLY" value="false" />
+    <option name="SKIP_NOOP_APK_INSTALLATIONS" value="true" />
+    <option name="FORCE_STOP_RUNNING_APP" value="true" />
+    <option name="TARGET_SELECTION_MODE" value="SHOW_DIALOG" />
+    <option name="USE_LAST_SELECTED_DEVICE" value="true" />
+    <option name="PREFERRED_AVD" value="" />
+    <option name="SELECTED_CLOUD_MATRIX_CONFIGURATION_ID" value="-1" />
+    <option name="SELECTED_CLOUD_MATRIX_PROJECT_ID" value="" />
+    <option name="DEBUGGER_TYPE" value="Auto" />
+    <Auto>
+      <option name="USE_JAVA_AWARE_DEBUGGER" value="false" />
+      <option name="SHOW_STATIC_VARS" value="true" />
+      <option name="WORKING_DIR" value="" />
+      <option name="TARGET_LOGGING_CHANNELS" value="lldb process:gdb-remote packets" />
+      <option name="SHOW_OPTIMIZED_WARNING" value="true" />
+    </Auto>
+    <Hybrid>
+      <option name="USE_JAVA_AWARE_DEBUGGER" value="false" />
+      <option name="SHOW_STATIC_VARS" value="true" />
+      <option name="WORKING_DIR" value="" />
+      <option name="TARGET_LOGGING_CHANNELS" value="lldb process:gdb-remote packets" />
+      <option name="SHOW_OPTIMIZED_WARNING" value="true" />
+    </Hybrid>
+    <Java />
+    <Native>
+      <option name="USE_JAVA_AWARE_DEBUGGER" value="false" />
+      <option name="SHOW_STATIC_VARS" value="true" />
+      <option name="WORKING_DIR" value="" />
+      <option name="TARGET_LOGGING_CHANNELS" value="lldb process:gdb-remote packets" />
+      <option name="SHOW_OPTIMIZED_WARNING" value="true" />
+    </Native>
+    <Profilers>
+      <option name="ENABLE_ADVANCED_PROFILING" value="true" />
+      <option name="SUPPORT_LIB_ENABLED" value="true" />
+      <option name="INSTRUMENTATION_ENABLED" value="true" />
+    </Profilers>
+    <option name="DEEP_LINK" value="" />
+    <option name="ACTIVITY_CLASS" value="com.example.android.persistence.codelab.step3_solution.BooksBorrowedByUserActivity" />
+    <method />
+  </configuration>
+</component>
\ No newline at end of file
diff --git a/samples-flatfoot/codelabs/persistence/.idea/runConfigurations/Step_4___Solution.xml b/samples-flatfoot/codelabs/persistence/.idea/runConfigurations/Step_4___Solution.xml
new file mode 100644
index 0000000..094bac9
--- /dev/null
+++ b/samples-flatfoot/codelabs/persistence/.idea/runConfigurations/Step_4___Solution.xml
@@ -0,0 +1,51 @@
+<component name="ProjectRunConfigurationManager">
+  <configuration default="false" name="Step 4 - Solution" type="AndroidRunConfigurationType" factoryName="Android App" activateToolWindowBeforeRun="false">
+    <module name="app" />
+    <option name="DEPLOY" value="true" />
+    <option name="ARTIFACT_NAME" value="" />
+    <option name="PM_INSTALL_OPTIONS" value="" />
+    <option name="ACTIVITY_EXTRA_FLAGS" value="" />
+    <option name="MODE" value="specific_activity" />
+    <option name="PREFERRED_AVD" value="" />
+    <option name="CLEAR_LOGCAT" value="false" />
+    <option name="SHOW_LOGCAT_AUTOMATICALLY" value="false" />
+    <option name="SKIP_NOOP_APK_INSTALLATIONS" value="true" />
+    <option name="FORCE_STOP_RUNNING_APP" value="true" />
+    <option name="TARGET_SELECTION_MODE" value="SHOW_DIALOG" />
+    <option name="USE_LAST_SELECTED_DEVICE" value="true" />
+    <option name="PREFERRED_AVD" value="" />
+    <option name="SELECTED_CLOUD_MATRIX_CONFIGURATION_ID" value="-1" />
+    <option name="SELECTED_CLOUD_MATRIX_PROJECT_ID" value="" />
+    <option name="DEBUGGER_TYPE" value="Auto" />
+    <Auto>
+      <option name="USE_JAVA_AWARE_DEBUGGER" value="false" />
+      <option name="SHOW_STATIC_VARS" value="true" />
+      <option name="WORKING_DIR" value="" />
+      <option name="TARGET_LOGGING_CHANNELS" value="lldb process:gdb-remote packets" />
+      <option name="SHOW_OPTIMIZED_WARNING" value="true" />
+    </Auto>
+    <Hybrid>
+      <option name="USE_JAVA_AWARE_DEBUGGER" value="false" />
+      <option name="SHOW_STATIC_VARS" value="true" />
+      <option name="WORKING_DIR" value="" />
+      <option name="TARGET_LOGGING_CHANNELS" value="lldb process:gdb-remote packets" />
+      <option name="SHOW_OPTIMIZED_WARNING" value="true" />
+    </Hybrid>
+    <Java />
+    <Native>
+      <option name="USE_JAVA_AWARE_DEBUGGER" value="false" />
+      <option name="SHOW_STATIC_VARS" value="true" />
+      <option name="WORKING_DIR" value="" />
+      <option name="TARGET_LOGGING_CHANNELS" value="lldb process:gdb-remote packets" />
+      <option name="SHOW_OPTIMIZED_WARNING" value="true" />
+    </Native>
+    <Profilers>
+      <option name="ENABLE_ADVANCED_PROFILING" value="true" />
+      <option name="SUPPORT_LIB_ENABLED" value="true" />
+      <option name="INSTRUMENTATION_ENABLED" value="true" />
+    </Profilers>
+    <option name="DEEP_LINK" value="" />
+    <option name="ACTIVITY_CLASS" value="com.example.android.persistence.codelab.step4_solution.TypeConvertersActivity" />
+    <method />
+  </configuration>
+</component>
\ No newline at end of file
diff --git a/samples-flatfoot/codelabs/persistence/.idea/runConfigurations/Step_4___Type_converters.xml b/samples-flatfoot/codelabs/persistence/.idea/runConfigurations/Step_4___Type_converters.xml
new file mode 100644
index 0000000..d75817c
--- /dev/null
+++ b/samples-flatfoot/codelabs/persistence/.idea/runConfigurations/Step_4___Type_converters.xml
@@ -0,0 +1,51 @@
+<component name="ProjectRunConfigurationManager">
+  <configuration default="false" name="Step 4 - Type converters" type="AndroidRunConfigurationType" factoryName="Android App" activateToolWindowBeforeRun="false">
+    <module name="app" />
+    <option name="DEPLOY" value="true" />
+    <option name="ARTIFACT_NAME" value="" />
+    <option name="PM_INSTALL_OPTIONS" value="" />
+    <option name="ACTIVITY_EXTRA_FLAGS" value="" />
+    <option name="MODE" value="specific_activity" />
+    <option name="PREFERRED_AVD" value="" />
+    <option name="CLEAR_LOGCAT" value="false" />
+    <option name="SHOW_LOGCAT_AUTOMATICALLY" value="false" />
+    <option name="SKIP_NOOP_APK_INSTALLATIONS" value="true" />
+    <option name="FORCE_STOP_RUNNING_APP" value="true" />
+    <option name="TARGET_SELECTION_MODE" value="SHOW_DIALOG" />
+    <option name="USE_LAST_SELECTED_DEVICE" value="true" />
+    <option name="PREFERRED_AVD" value="" />
+    <option name="SELECTED_CLOUD_MATRIX_CONFIGURATION_ID" value="-1" />
+    <option name="SELECTED_CLOUD_MATRIX_PROJECT_ID" value="" />
+    <option name="DEBUGGER_TYPE" value="Auto" />
+    <Auto>
+      <option name="USE_JAVA_AWARE_DEBUGGER" value="false" />
+      <option name="SHOW_STATIC_VARS" value="true" />
+      <option name="WORKING_DIR" value="" />
+      <option name="TARGET_LOGGING_CHANNELS" value="lldb process:gdb-remote packets" />
+      <option name="SHOW_OPTIMIZED_WARNING" value="true" />
+    </Auto>
+    <Hybrid>
+      <option name="USE_JAVA_AWARE_DEBUGGER" value="false" />
+      <option name="SHOW_STATIC_VARS" value="true" />
+      <option name="WORKING_DIR" value="" />
+      <option name="TARGET_LOGGING_CHANNELS" value="lldb process:gdb-remote packets" />
+      <option name="SHOW_OPTIMIZED_WARNING" value="true" />
+    </Hybrid>
+    <Java />
+    <Native>
+      <option name="USE_JAVA_AWARE_DEBUGGER" value="false" />
+      <option name="SHOW_STATIC_VARS" value="true" />
+      <option name="WORKING_DIR" value="" />
+      <option name="TARGET_LOGGING_CHANNELS" value="lldb process:gdb-remote packets" />
+      <option name="SHOW_OPTIMIZED_WARNING" value="true" />
+    </Native>
+    <Profilers>
+      <option name="ENABLE_ADVANCED_PROFILING" value="true" />
+      <option name="SUPPORT_LIB_ENABLED" value="true" />
+      <option name="INSTRUMENTATION_ENABLED" value="true" />
+    </Profilers>
+    <option name="DEEP_LINK" value="" />
+    <option name="ACTIVITY_CLASS" value="com.example.android.persistence.codelab.step4.TypeConvertersActivity" />
+    <method />
+  </configuration>
+</component>
\ No newline at end of file
diff --git a/samples-flatfoot/codelabs/persistence/.idea/runConfigurations/Step_5___Custom_Results.xml b/samples-flatfoot/codelabs/persistence/.idea/runConfigurations/Step_5___Custom_Results.xml
new file mode 100644
index 0000000..3dcf7f5
--- /dev/null
+++ b/samples-flatfoot/codelabs/persistence/.idea/runConfigurations/Step_5___Custom_Results.xml
@@ -0,0 +1,51 @@
+<component name="ProjectRunConfigurationManager">
+  <configuration default="false" name="Step 5 - Custom Results" type="AndroidRunConfigurationType" factoryName="Android App" activateToolWindowBeforeRun="false">
+    <module name="app" />
+    <option name="DEPLOY" value="true" />
+    <option name="ARTIFACT_NAME" value="" />
+    <option name="PM_INSTALL_OPTIONS" value="" />
+    <option name="ACTIVITY_EXTRA_FLAGS" value="" />
+    <option name="MODE" value="specific_activity" />
+    <option name="PREFERRED_AVD" value="" />
+    <option name="CLEAR_LOGCAT" value="false" />
+    <option name="SHOW_LOGCAT_AUTOMATICALLY" value="false" />
+    <option name="SKIP_NOOP_APK_INSTALLATIONS" value="true" />
+    <option name="FORCE_STOP_RUNNING_APP" value="true" />
+    <option name="TARGET_SELECTION_MODE" value="SHOW_DIALOG" />
+    <option name="USE_LAST_SELECTED_DEVICE" value="true" />
+    <option name="PREFERRED_AVD" value="" />
+    <option name="SELECTED_CLOUD_MATRIX_CONFIGURATION_ID" value="-1" />
+    <option name="SELECTED_CLOUD_MATRIX_PROJECT_ID" value="" />
+    <option name="DEBUGGER_TYPE" value="Auto" />
+    <Auto>
+      <option name="USE_JAVA_AWARE_DEBUGGER" value="false" />
+      <option name="SHOW_STATIC_VARS" value="true" />
+      <option name="WORKING_DIR" value="" />
+      <option name="TARGET_LOGGING_CHANNELS" value="lldb process:gdb-remote packets" />
+      <option name="SHOW_OPTIMIZED_WARNING" value="true" />
+    </Auto>
+    <Hybrid>
+      <option name="USE_JAVA_AWARE_DEBUGGER" value="false" />
+      <option name="SHOW_STATIC_VARS" value="true" />
+      <option name="WORKING_DIR" value="" />
+      <option name="TARGET_LOGGING_CHANNELS" value="lldb process:gdb-remote packets" />
+      <option name="SHOW_OPTIMIZED_WARNING" value="true" />
+    </Hybrid>
+    <Java />
+    <Native>
+      <option name="USE_JAVA_AWARE_DEBUGGER" value="false" />
+      <option name="SHOW_STATIC_VARS" value="true" />
+      <option name="WORKING_DIR" value="" />
+      <option name="TARGET_LOGGING_CHANNELS" value="lldb process:gdb-remote packets" />
+      <option name="SHOW_OPTIMIZED_WARNING" value="true" />
+    </Native>
+    <Profilers>
+      <option name="ENABLE_ADVANCED_PROFILING" value="true" />
+      <option name="SUPPORT_LIB_ENABLED" value="true" />
+      <option name="INSTRUMENTATION_ENABLED" value="true" />
+    </Profilers>
+    <option name="DEEP_LINK" value="" />
+    <option name="ACTIVITY_CLASS" value="com.example.android.persistence.codelab.step5.CustomResultActivity" />
+    <method />
+  </configuration>
+</component>
\ No newline at end of file
diff --git a/samples-flatfoot/codelabs/persistence/.idea/runConfigurations/Step_5___Solution.xml b/samples-flatfoot/codelabs/persistence/.idea/runConfigurations/Step_5___Solution.xml
new file mode 100644
index 0000000..790b28c
--- /dev/null
+++ b/samples-flatfoot/codelabs/persistence/.idea/runConfigurations/Step_5___Solution.xml
@@ -0,0 +1,51 @@
+<component name="ProjectRunConfigurationManager">
+  <configuration default="false" name="Step 5 - Solution" type="AndroidRunConfigurationType" factoryName="Android App" activateToolWindowBeforeRun="false">
+    <module name="app" />
+    <option name="DEPLOY" value="true" />
+    <option name="ARTIFACT_NAME" value="" />
+    <option name="PM_INSTALL_OPTIONS" value="" />
+    <option name="ACTIVITY_EXTRA_FLAGS" value="" />
+    <option name="MODE" value="specific_activity" />
+    <option name="PREFERRED_AVD" value="" />
+    <option name="CLEAR_LOGCAT" value="false" />
+    <option name="SHOW_LOGCAT_AUTOMATICALLY" value="false" />
+    <option name="SKIP_NOOP_APK_INSTALLATIONS" value="true" />
+    <option name="FORCE_STOP_RUNNING_APP" value="true" />
+    <option name="TARGET_SELECTION_MODE" value="SHOW_DIALOG" />
+    <option name="USE_LAST_SELECTED_DEVICE" value="true" />
+    <option name="PREFERRED_AVD" value="" />
+    <option name="SELECTED_CLOUD_MATRIX_CONFIGURATION_ID" value="-1" />
+    <option name="SELECTED_CLOUD_MATRIX_PROJECT_ID" value="" />
+    <option name="DEBUGGER_TYPE" value="Auto" />
+    <Auto>
+      <option name="USE_JAVA_AWARE_DEBUGGER" value="false" />
+      <option name="SHOW_STATIC_VARS" value="true" />
+      <option name="WORKING_DIR" value="" />
+      <option name="TARGET_LOGGING_CHANNELS" value="lldb process:gdb-remote packets" />
+      <option name="SHOW_OPTIMIZED_WARNING" value="true" />
+    </Auto>
+    <Hybrid>
+      <option name="USE_JAVA_AWARE_DEBUGGER" value="false" />
+      <option name="SHOW_STATIC_VARS" value="true" />
+      <option name="WORKING_DIR" value="" />
+      <option name="TARGET_LOGGING_CHANNELS" value="lldb process:gdb-remote packets" />
+      <option name="SHOW_OPTIMIZED_WARNING" value="true" />
+    </Hybrid>
+    <Java />
+    <Native>
+      <option name="USE_JAVA_AWARE_DEBUGGER" value="false" />
+      <option name="SHOW_STATIC_VARS" value="true" />
+      <option name="WORKING_DIR" value="" />
+      <option name="TARGET_LOGGING_CHANNELS" value="lldb process:gdb-remote packets" />
+      <option name="SHOW_OPTIMIZED_WARNING" value="true" />
+    </Native>
+    <Profilers>
+      <option name="ENABLE_ADVANCED_PROFILING" value="false" />
+      <option name="SUPPORT_LIB_ENABLED" value="true" />
+      <option name="INSTRUMENTATION_ENABLED" value="true" />
+    </Profilers>
+    <option name="DEEP_LINK" value="" />
+    <option name="ACTIVITY_CLASS" value="com.example.android.persistence.codelab.step5_solution.CustomResultUserActivity" />
+    <method />
+  </configuration>
+</component>
\ No newline at end of file
diff --git a/samples-flatfoot/codelabs/persistence/CONTRIBUTING.md b/samples-flatfoot/codelabs/persistence/CONTRIBUTING.md
new file mode 100644
index 0000000..7b86f95
--- /dev/null
+++ b/samples-flatfoot/codelabs/persistence/CONTRIBUTING.md
@@ -0,0 +1,35 @@
+# How to become a contributor and submit your own code
+
+## Contributor License Agreements
+
+We'd love to accept your sample apps and patches! Before we can take them, we
+have to jump a couple of legal hurdles.
+
+Please fill out either the individual or corporate Contributor License Agreement (CLA).
+
+  * If you are an individual writing original source code and you're sure you
+    own the intellectual property, then you'll need to sign an [individual CLA]
+    (https://cla.developers.google.com).
+  * If you work for a company that wants to allow you to contribute your work,
+    then you'll need to sign a [corporate CLA]
+    (https://cla.developers.google.com).
+  * Please make sure you sign both, Android and Google CLA
+
+Follow either of the two links above to access the appropriate CLA and
+instructions for how to sign and return it. Once we receive it, we'll be able to
+accept your pull requests.
+
+## Contributing A Patch
+
+1. Submit an issue describing your proposed change to the repo in question.
+1. The repo owner will respond to your issue promptly.
+1. If your proposed change is accepted, and you haven't already done so, sign a
+   Contributor License Agreement (see details above).
+1. Fork the desired repo, develop and test your code changes.
+1. Ensure that your code adheres to the existing style in the sample to which
+   you are contributing. Refer to the
+   [Android Code Style Guide]
+   (https://source.android.com/source/code-style.html) for the
+   recommended coding standards for this organization.
+1. Ensure that your code has an appropriate set of unit tests which all pass.
+1. Submit a pull request.
diff --git a/samples-flatfoot/codelabs/persistence/LICENSE b/samples-flatfoot/codelabs/persistence/LICENSE
new file mode 100644
index 0000000..1af981f
--- /dev/null
+++ b/samples-flatfoot/codelabs/persistence/LICENSE
@@ -0,0 +1,201 @@
+                                 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
+
+   APPENDIX: How to apply the Apache License to your work.
+
+      To apply the Apache License to your work, attach the following
+      boilerplate notice, with the fields enclosed by brackets "[]"
+      replaced with your own identifying information. (Don't include
+      the brackets!)  The text should be enclosed in the appropriate
+      comment syntax for the file format. We also recommend that a
+      file or class name and description of purpose be included on the
+      same "printed page" as the copyright notice for easier
+      identification within third-party archives.
+
+   Copyright 2014 The Android Open Source Project
+
+   Licensed under the Apache License, Version 2.0 (the "License");
+   you may not use this file except in compliance with the License.
+   You may obtain a copy of the License at
+
+       http://www.apache.org/licenses/LICENSE-2.0
+
+   Unless required by applicable law or agreed to in writing, software
+   distributed under the License is distributed on an "AS IS" BASIS,
+   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+   See the License for the specific language governing permissions and
+   limitations under the License.
diff --git a/samples-flatfoot/codelabs/persistence/app/build.gradle b/samples-flatfoot/codelabs/persistence/app/build.gradle
new file mode 100644
index 0000000..0a382b4
--- /dev/null
+++ b/samples-flatfoot/codelabs/persistence/app/build.gradle
@@ -0,0 +1,61 @@
+/*
+ * Copyright 2017, The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+apply plugin: 'com.android.application'
+
+android {
+    compileSdkVersion 25
+    buildToolsVersion "25.0.2"
+    defaultConfig {
+        applicationId 'com.example.android.codelabs.persistence'
+        minSdkVersion 21
+        targetSdkVersion 25
+        versionCode 1
+        versionName "1.0"
+        testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
+    }
+    buildTypes {
+        release {
+            minifyEnabled false
+            proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
+        }
+    }
+    dataBinding {
+        enabled = true
+    }
+    productFlavors {
+    }
+    // To avoid lint errors on generated sources, we temporarily disable abortOnError
+    // until the fix is released.
+    lintOptions {
+        abortOnError false
+    }
+}
+
+dependencies {
+    compile fileTree(include: ['*.jar'], dir: 'libs')
+    androidTestCompile('com.android.support.test.espresso:espresso-core:' + rootProject.espressoVersion, {
+        exclude group: 'com.android.support', module: 'support-annotations'
+    })
+    compile 'com.android.support:appcompat-v7:' + rootProject.supportLibVersion;
+    compile 'com.android.support:cardview-v7:' + rootProject.supportLibVersion;
+    compile 'com.android.support:recyclerview-v7:' + rootProject.supportLibVersion;
+    testCompile 'junit:junit:4.12'
+    compile 'android.arch.lifecycle:extensions:' + rootProject.archLifecycleVersion;
+    compile 'android.arch.persistence.room:runtime:' + rootProject.archRoomVersion;
+    annotationProcessor 'android.arch.lifecycle:compiler:' + rootProject.archLifecycleVersion;
+    annotationProcessor 'android.arch.persistence.room:compiler:' + rootProject.archRoomVersion;
+}
diff --git a/samples-flatfoot/codelabs/persistence/app/proguard-rules.pro b/samples-flatfoot/codelabs/persistence/app/proguard-rules.pro
new file mode 100644
index 0000000..4cb7103
--- /dev/null
+++ b/samples-flatfoot/codelabs/persistence/app/proguard-rules.pro
@@ -0,0 +1,17 @@
+# Add project specific ProGuard rules here.
+# By default, the flags in this file are appended to flags specified
+# in /usr/local/google/home/jalc/sw/android-sdks/android-sdk-linux/tools/proguard/proguard-android.txt
+# You can edit the include path and order by changing the proguardFiles
+# directive in build.gradle.
+#
+# For more details, see
+#   http://developer.android.com/guide/developing/tools/proguard.html
+
+# Add any project specific keep options here:
+
+# If your project uses WebView with JS, uncomment the following
+# and specify the fully qualified class name to the JavaScript interface
+# class:
+#-keepclassmembers class fqcn.of.javascript.interface.for.webview {
+#   public *;
+#}
diff --git a/samples-flatfoot/codelabs/persistence/app/src/main/AndroidManifest.xml b/samples-flatfoot/codelabs/persistence/app/src/main/AndroidManifest.xml
new file mode 100644
index 0000000..ade902e
--- /dev/null
+++ b/samples-flatfoot/codelabs/persistence/app/src/main/AndroidManifest.xml
@@ -0,0 +1,79 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  ~ Copyright 2017, The Android Open Source Project
+  ~
+  ~ Licensed under the Apache License, Version 2.0 (the "License");
+  ~ you may not use this file except in compliance with the License.
+  ~ You may obtain a copy of the License at
+  ~
+  ~      http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing, software
+  ~ distributed under the License is distributed on an "AS IS" BASIS,
+  ~ WITHOUT 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.codelabs.persistence">
+
+    <uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
+    <uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" />
+
+    <uses-feature android:name="android.hardware.location.gps" />
+
+    <application
+        android:allowBackup="true"
+        android:icon="@mipmap/ic_launcher"
+        android:label="@string/app_name"
+        android:supportsRtl="true"
+        android:theme="@style/AppTheme">
+        <activity android:name="com.example.android.persistence.codelab.step1_solution.UsersActivity">
+            <intent-filter>
+                <action android:name="android.intent.action.MAIN"/>
+            </intent-filter>
+        </activity>
+        <activity android:name="com.example.android.persistence.codelab.step1.UsersActivity">
+            <intent-filter>
+                <action android:name="android.intent.action.MAIN"/>
+            </intent-filter>
+        </activity>
+        <activity android:name="com.example.android.persistence.codelab.step2.JankShowUserActivity">
+            <intent-filter>
+                <action android:name="android.intent.action.MAIN"/>
+            </intent-filter>
+        </activity>
+        <activity android:name="com.example.android.persistence.codelab.step3.BooksBorrowedByUserActivity">
+            <intent-filter>
+                <action android:name="android.intent.action.MAIN"/>
+            </intent-filter>
+        </activity>
+        <activity android:name="com.example.android.persistence.codelab.step3_solution.BooksBorrowedByUserActivity">
+            <intent-filter>
+                <action android:name="android.intent.action.MAIN"/>
+            </intent-filter>
+        </activity>
+        <activity android:name="com.example.android.persistence.codelab.step4.TypeConvertersActivity">
+            <intent-filter>
+                <action android:name="android.intent.action.MAIN"/>
+            </intent-filter>
+        </activity>
+        <activity android:name="com.example.android.persistence.codelab.step4_solution.TypeConvertersActivity">
+            <intent-filter>
+                <action android:name="android.intent.action.MAIN"/>
+            </intent-filter>
+        </activity>
+        <activity android:name="com.example.android.persistence.codelab.step5_solution.CustomResultUserActivity">
+            <intent-filter>
+                <action android:name="android.intent.action.MAIN"/>
+            </intent-filter>
+        </activity>
+        <activity android:name="com.example.android.persistence.codelab.step5.CustomResultActivity">
+            <intent-filter>
+                <action android:name="android.intent.action.MAIN"/>
+            </intent-filter>
+        </activity>
+    </application>
+
+</manifest>
\ No newline at end of file
diff --git a/samples-flatfoot/codelabs/persistence/app/src/main/java/com/example/android/persistence/codelab/db/AppDatabase.java b/samples-flatfoot/codelabs/persistence/app/src/main/java/com/example/android/persistence/codelab/db/AppDatabase.java
new file mode 100644
index 0000000..4399a84
--- /dev/null
+++ b/samples-flatfoot/codelabs/persistence/app/src/main/java/com/example/android/persistence/codelab/db/AppDatabase.java
@@ -0,0 +1,45 @@
+/*
+ * Copyright 2017, The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES 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.persistence.codelab.db;
+
+import android.content.Context;
+
+import android.arch.persistence.room.Database;
+import android.arch.persistence.room.Room;
+import android.arch.persistence.room.RoomDatabase;
+
+@Database(entities = {User.class, Book.class, Loan.class}, version = 1)
+public abstract class AppDatabase extends RoomDatabase {
+
+    private static AppDatabase INSTANCE;
+
+    public abstract UserDao userModel();
+    public abstract BookDao bookModel();
+    public abstract LoanDao loanModel();
+
+    public static AppDatabase getInMemoryDatabase(Context context) {
+        if (INSTANCE == null) {
+            INSTANCE = Room.inMemoryDatabaseBuilder(
+                    context.getApplicationContext(), AppDatabase.class).build();
+        }
+        return INSTANCE;
+    }
+
+    public static void destroyInstance() {
+        INSTANCE = null;
+    }
+}
\ No newline at end of file
diff --git a/samples-flatfoot/codelabs/persistence/app/src/main/java/com/example/android/persistence/codelab/db/Book.java b/samples-flatfoot/codelabs/persistence/app/src/main/java/com/example/android/persistence/codelab/db/Book.java
new file mode 100644
index 0000000..5b3cd39
--- /dev/null
+++ b/samples-flatfoot/codelabs/persistence/app/src/main/java/com/example/android/persistence/codelab/db/Book.java
@@ -0,0 +1,26 @@
+/*
+ * Copyright 2017, The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES 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.persistence.codelab.db;
+
+import android.arch.persistence.room.Entity;
+import android.arch.persistence.room.PrimaryKey;
+
+@Entity
+public class Book {
+    public @PrimaryKey String id;
+    public String title;
+}
diff --git a/samples-flatfoot/codelabs/persistence/app/src/main/java/com/example/android/persistence/codelab/db/BookDao.java b/samples-flatfoot/codelabs/persistence/app/src/main/java/com/example/android/persistence/codelab/db/BookDao.java
new file mode 100644
index 0000000..07a419a
--- /dev/null
+++ b/samples-flatfoot/codelabs/persistence/app/src/main/java/com/example/android/persistence/codelab/db/BookDao.java
@@ -0,0 +1,96 @@
+/*
+ * Copyright 2017, The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES 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.persistence.codelab.db;
+
+import android.arch.lifecycle.LiveData;
+import android.arch.persistence.room.Dao;
+import android.arch.persistence.room.Insert;
+import android.arch.persistence.room.Query;
+import android.arch.persistence.room.TypeConverters;
+import android.arch.persistence.room.Update;
+
+import java.util.Date;
+import java.util.List;
+
+import static android.arch.persistence.room.OnConflictStrategy.IGNORE;
+import static android.arch.persistence.room.OnConflictStrategy.REPLACE;
+
+
+@Dao
+@TypeConverters(DateConverter.class)
+public interface BookDao {
+
+    @Query("select * from Book where id = :id")
+    User loadUserById(int id);
+
+    @Query("SELECT * FROM Book " +
+            "INNER JOIN Loan ON Loan.book_id = Book.id " +
+            "INNER JOIN User on User.id = Loan.user_id " +
+            "WHERE User.name LIKE :userName"
+    )
+    public LiveData<List<Book>> findBooksBorrowedByName(String userName);
+
+    @Query("SELECT * FROM Book " +
+            "INNER JOIN Loan ON Loan.book_id = Book.id " +
+            "INNER JOIN User on User.id = Loan.user_id " +
+            "WHERE User.name LIKE :userName " +
+            "AND Loan.endTime > :after "
+    )
+    public LiveData<List<Book>> findBooksBorrowedByNameAfter(String userName, Date after);
+
+    @Query("SELECT * FROM Book " +
+            "INNER JOIN Loan ON Loan.book_id = Book.id " +
+            "INNER JOIN User on User.id = Loan.user_id " +
+            "WHERE User.name LIKE :userName"
+    )
+    public List<Book> findBooksBorrowedByNameSync(String userName);
+
+    @Query("SELECT * FROM Book " +
+            "INNER JOIN Loan ON Loan.book_id LIKE Book.id " +
+            "WHERE Loan.user_id LIKE :userId "
+    )
+    public LiveData<List<Book>> findBooksBorrowedByUser(String userId);
+
+    @Query("SELECT * FROM Book " +
+            "INNER JOIN Loan ON Loan.book_id LIKE Book.id " +
+            "WHERE Loan.user_id LIKE :userId " +
+            "AND Loan.endTime > :after "
+    )
+    public LiveData<List<Book>> findBooksBorrowedByUserAfter(String userId, Date after);
+
+    @Query("SELECT * FROM Book " +
+            "INNER JOIN Loan ON Loan.book_id LIKE Book.id " +
+            "WHERE Loan.user_id LIKE :userId "
+    )
+    public List<Book> findBooksBorrowedByUserSync(String userId);
+
+    @Query("SELECT * FROM Book")
+    public LiveData<List<Book>> findAllBooks();
+
+
+    @Query("SELECT * FROM Book")
+    public List<Book> findAllBooksSync();
+
+    @Insert(onConflict = IGNORE)
+    void insertBook(Book book);
+
+    @Update(onConflict = REPLACE)
+    void updateBook(Book book);
+
+    @Query("DELETE FROM Book")
+    void deleteAll();
+}
diff --git a/samples-flatfoot/codelabs/persistence/app/src/main/java/com/example/android/persistence/codelab/db/DateConverter.java b/samples-flatfoot/codelabs/persistence/app/src/main/java/com/example/android/persistence/codelab/db/DateConverter.java
new file mode 100644
index 0000000..f7ad1d6
--- /dev/null
+++ b/samples-flatfoot/codelabs/persistence/app/src/main/java/com/example/android/persistence/codelab/db/DateConverter.java
@@ -0,0 +1,33 @@
+/*
+ * Copyright 2017, The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES 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.persistence.codelab.db;
+
+import android.arch.persistence.room.TypeConverter;
+
+import java.util.Date;
+
+public class DateConverter {
+    @TypeConverter
+    public static Date toDate(Long timestamp) {
+        return timestamp == null ? null : new Date(timestamp);
+    }
+
+    @TypeConverter
+    public static Long toTimestamp(Date date) {
+        return date == null ? null : date.getTime();
+    }
+}
diff --git a/samples-flatfoot/codelabs/persistence/app/src/main/java/com/example/android/persistence/codelab/db/Loan.java b/samples-flatfoot/codelabs/persistence/app/src/main/java/com/example/android/persistence/codelab/db/Loan.java
new file mode 100644
index 0000000..39e8a89
--- /dev/null
+++ b/samples-flatfoot/codelabs/persistence/app/src/main/java/com/example/android/persistence/codelab/db/Loan.java
@@ -0,0 +1,45 @@
+/*
+ * Copyright 2017, The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES 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.persistence.codelab.db;
+
+import android.arch.persistence.room.ColumnInfo;
+import android.arch.persistence.room.Entity;
+import android.arch.persistence.room.ForeignKey;
+import android.arch.persistence.room.PrimaryKey;
+import android.arch.persistence.room.TypeConverters;
+
+import java.util.Date;
+
+@Entity(foreignKeys = {
+        @ForeignKey(entity = Book.class,
+                parentColumns = "id",
+                childColumns = "book_id"),
+
+        @ForeignKey(entity = User.class,
+                parentColumns = "id",
+                childColumns = "user_id")})
+@TypeConverters(DateConverter.class)
+public class Loan {
+    // Fields can be public or private with getters and setters.
+    public @PrimaryKey String id;
+    public Date startTime;
+    public Date endTime;
+    @ColumnInfo(name="book_id")
+    public String bookId;
+    @ColumnInfo(name="user_id")
+    public String userId;
+}
diff --git a/samples-flatfoot/codelabs/persistence/app/src/main/java/com/example/android/persistence/codelab/db/LoanDao.java b/samples-flatfoot/codelabs/persistence/app/src/main/java/com/example/android/persistence/codelab/db/LoanDao.java
new file mode 100644
index 0000000..5a8f9ed
--- /dev/null
+++ b/samples-flatfoot/codelabs/persistence/app/src/main/java/com/example/android/persistence/codelab/db/LoanDao.java
@@ -0,0 +1,57 @@
+/*
+ * Copyright 2017, The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES 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.persistence.codelab.db;
+
+
+import android.arch.lifecycle.LiveData;
+import android.arch.persistence.room.Dao;
+import android.arch.persistence.room.Insert;
+import android.arch.persistence.room.Query;
+import android.arch.persistence.room.TypeConverters;
+
+import java.util.Date;
+import java.util.List;
+
+import static android.arch.persistence.room.OnConflictStrategy.ABORT;
+
+@Dao
+@TypeConverters(DateConverter.class)
+public interface LoanDao {
+
+    @Query("SELECT * From Loan")
+    LiveData<List<Loan>> findAll();
+
+    @Query("SELECT Loan.id, Book.title, User.name, Loan.startTime, Loan.endTime From Loan " +
+        "INNER JOIN Book ON Loan.book_id = Book.id " +
+        "INNER JOIN User ON Loan.user_id = User.id ")
+    LiveData<List<LoanWithUserAndBook>> findAllWithUserAndBook();
+
+    @Query("SELECT Loan.id, Book.title as title, User.name as name, Loan.startTime, Loan.endTime " +
+            "FROM Book " +
+            "INNER JOIN Loan ON Loan.book_id = Book.id " +
+            "INNER JOIN User on User.id = Loan.user_id " +
+            "WHERE User.name LIKE :userName " +
+            "AND Loan.endTime > :after "
+    )
+    public LiveData<List<LoanWithUserAndBook>> findLoansByNameAfter(String userName, Date after);
+
+    @Insert(onConflict = ABORT)
+    void insertLoan(Loan loan);
+
+    @Query("DELETE FROM Loan")
+    void deleteAll();
+}
diff --git a/samples-flatfoot/codelabs/persistence/app/src/main/java/com/example/android/persistence/codelab/db/LoanWithUserAndBook.java b/samples-flatfoot/codelabs/persistence/app/src/main/java/com/example/android/persistence/codelab/db/LoanWithUserAndBook.java
new file mode 100644
index 0000000..9df2dda
--- /dev/null
+++ b/samples-flatfoot/codelabs/persistence/app/src/main/java/com/example/android/persistence/codelab/db/LoanWithUserAndBook.java
@@ -0,0 +1,34 @@
+/*
+ * Copyright 2017, The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES 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.persistence.codelab.db;
+
+import android.arch.persistence.room.ColumnInfo;
+import android.arch.persistence.room.TypeConverters;
+
+import java.util.Date;
+
+public class LoanWithUserAndBook {
+    public String id;
+    @ColumnInfo(name="title")
+    public String bookTitle;
+    @ColumnInfo(name="name")
+    public String userName;
+    @TypeConverters(DateConverter.class)
+    public Date startTime;
+    @TypeConverters(DateConverter.class)
+    public Date endTime;
+}
diff --git a/samples-flatfoot/codelabs/persistence/app/src/main/java/com/example/android/persistence/codelab/db/User.java b/samples-flatfoot/codelabs/persistence/app/src/main/java/com/example/android/persistence/codelab/db/User.java
new file mode 100644
index 0000000..75dc4b3
--- /dev/null
+++ b/samples-flatfoot/codelabs/persistence/app/src/main/java/com/example/android/persistence/codelab/db/User.java
@@ -0,0 +1,29 @@
+/*
+ * Copyright 2017, The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES 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.persistence.codelab.db;
+
+import android.arch.persistence.room.Entity;
+import android.arch.persistence.room.PrimaryKey;
+
+
+@Entity
+public class User {
+    public @PrimaryKey String id;
+    public String name;
+    public String lastName;
+    public int age;
+}
diff --git a/samples-flatfoot/codelabs/persistence/app/src/main/java/com/example/android/persistence/codelab/db/UserDao.java b/samples-flatfoot/codelabs/persistence/app/src/main/java/com/example/android/persistence/codelab/db/UserDao.java
new file mode 100644
index 0000000..3dd836d
--- /dev/null
+++ b/samples-flatfoot/codelabs/persistence/app/src/main/java/com/example/android/persistence/codelab/db/UserDao.java
@@ -0,0 +1,62 @@
+/*
+ * Copyright 2017, The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES 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.persistence.codelab.db;
+
+import android.arch.persistence.room.Dao;
+import android.arch.persistence.room.Delete;
+import android.arch.persistence.room.Insert;
+import android.arch.persistence.room.Query;
+
+import java.util.List;
+
+import static android.arch.persistence.room.OnConflictStrategy.IGNORE;
+
+@Dao
+public interface UserDao {
+    @Query("select * from user")
+    List<User> loadAllUsers();
+
+    @Query("select * from user where id = :id")
+    User loadUserById(int id);
+
+    @Query("select * from user where name = :firstName and lastName = :lastName")
+    List<User> findByNameAndLastName(String firstName, String lastName);
+
+    @Insert(onConflict = IGNORE)
+    void insertUser(User user);
+
+    @Delete
+    void deleteUser(User user);
+
+    @Query("delete from user where name like :badName OR lastName like :badName")
+    int deleteUsersByName(String badName);
+
+    @Insert(onConflict = IGNORE)
+    void insertOrReplaceUsers(User... users);
+
+    @Delete
+    void deleteUsers(User user1, User user2);
+
+    @Query("SELECT * FROM User WHERE :age == :age") // TODO: Fix this!
+    List<User> findYoungerThan(int age);
+
+    @Query("SELECT * FROM User WHERE age < :age")
+    List<User> findYoungerThanSolution(int age);
+
+    @Query("DELETE FROM User")
+    void deleteAll();
+}
\ No newline at end of file
diff --git a/samples-flatfoot/codelabs/persistence/app/src/main/java/com/example/android/persistence/codelab/db/utils/DatabaseInitializer.java b/samples-flatfoot/codelabs/persistence/app/src/main/java/com/example/android/persistence/codelab/db/utils/DatabaseInitializer.java
new file mode 100644
index 0000000..7ced200
--- /dev/null
+++ b/samples-flatfoot/codelabs/persistence/app/src/main/java/com/example/android/persistence/codelab/db/utils/DatabaseInitializer.java
@@ -0,0 +1,136 @@
+/*
+ * Copyright 2017, The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES 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.persistence.codelab.db.utils;
+
+import android.os.AsyncTask;
+import android.support.annotation.NonNull;
+import android.util.Log;
+
+import com.example.android.persistence.codelab.db.AppDatabase;
+import com.example.android.persistence.codelab.db.Book;
+import com.example.android.persistence.codelab.db.Loan;
+import com.example.android.persistence.codelab.db.User;
+
+import java.util.Calendar;
+import java.util.Date;
+
+public class DatabaseInitializer {
+
+    // Simulate a blocking operation delaying each Loan insertion with a delay:
+    private static final int DELAY_MILLIS = 500;
+
+    public static void populateAsync(final AppDatabase db) {
+
+        PopulateDbAsync task = new PopulateDbAsync(db);
+        task.execute();
+    }
+
+    public static void populateSync(@NonNull final AppDatabase db) {
+        populateWithTestData(db);
+    }
+
+    private static void addLoan(final AppDatabase db, final String id,
+                                final User user, final Book book, Date from, Date to) {
+        Loan loan = new Loan();
+        loan.id = id;
+        loan.bookId = book.id;
+        loan.userId = user.id;
+        loan.startTime = from;
+        loan.endTime = to;
+        db.loanModel().insertLoan(loan);
+    }
+
+    private static Book addBook(final AppDatabase db, final String id, final String title) {
+        Book book = new Book();
+        book.id = id;
+        book.title = title;
+        db.bookModel().insertBook(book);
+        return book;
+    }
+
+    private static User addUser(final AppDatabase db, final String id, final String name,
+                                final String lastName, final int age) {
+        User user = new User();
+        user.id = id;
+        user.age = age;
+        user.name = name;
+        user.lastName = lastName;
+        db.userModel().insertUser(user);
+        return user;
+    }
+
+    private static void populateWithTestData(AppDatabase db) {
+        db.loanModel().deleteAll();
+        db.userModel().deleteAll();
+        db.bookModel().deleteAll();
+
+        User user1 = addUser(db, "1", "Jason", "Seaver", 40);
+        User user2 = addUser(db, "2", "Mike", "Seaver", 12);
+        addUser(db, "3", "Carol", "Seaver", 15);
+
+        Book book1 = addBook(db, "1", "Dune");
+        Book book2 = addBook(db, "2", "1984");
+        Book book3 = addBook(db, "3", "The War of the Worlds");
+        Book book4 = addBook(db, "4", "Brave New World");
+        addBook(db, "5", "Foundation");
+        try {
+            // Loans are added with a delay, to have time for the UI to react to changes.
+
+            Date today = getTodayPlusDays(0);
+            Date yesterday = getTodayPlusDays(-1);
+            Date twoDaysAgo = getTodayPlusDays(-2);
+            Date lastWeek = getTodayPlusDays(-7);
+            Date twoWeeksAgo = getTodayPlusDays(-14);
+
+            addLoan(db, "1", user1, book1, twoWeeksAgo, lastWeek);
+            Thread.sleep(DELAY_MILLIS);
+            addLoan(db, "2", user2, book1, lastWeek, yesterday);
+            Thread.sleep(DELAY_MILLIS);
+            addLoan(db, "3", user2, book2, lastWeek, today);
+            Thread.sleep(DELAY_MILLIS);
+            addLoan(db, "4", user2, book3, lastWeek, twoDaysAgo);
+            Thread.sleep(DELAY_MILLIS);
+            addLoan(db, "5", user2, book4, lastWeek, today);
+            Log.d("DB", "Added loans");
+
+        } catch (InterruptedException e) {
+            e.printStackTrace();
+        }
+    }
+
+    private static Date getTodayPlusDays(int daysAgo) {
+        Calendar calendar = Calendar.getInstance();
+        calendar.set(Calendar.DATE, daysAgo);
+        return calendar.getTime();
+    }
+
+    private static class PopulateDbAsync extends AsyncTask<Void, Void, Void> {
+
+        private final AppDatabase mDb;
+
+        PopulateDbAsync(AppDatabase db) {
+            mDb = db;
+        }
+
+        @Override
+        protected Void doInBackground(final Void... params) {
+            populateWithTestData(mDb);
+            return null;
+        }
+
+    }
+}
diff --git a/samples-flatfoot/codelabs/persistence/app/src/main/java/com/example/android/persistence/codelab/step1/UsersActivity.java b/samples-flatfoot/codelabs/persistence/app/src/main/java/com/example/android/persistence/codelab/step1/UsersActivity.java
new file mode 100644
index 0000000..3a7f118
--- /dev/null
+++ b/samples-flatfoot/codelabs/persistence/app/src/main/java/com/example/android/persistence/codelab/step1/UsersActivity.java
@@ -0,0 +1,73 @@
+/*
+ * Copyright 2017, The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES 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.persistence.codelab.step1;
+
+import android.os.Bundle;
+import android.widget.TextView;
+
+import android.arch.lifecycle.LifecycleActivity;
+import com.example.android.codelabs.persistence.R;
+import com.example.android.persistence.codelab.db.AppDatabase;
+import com.example.android.persistence.codelab.db.User;
+import com.example.android.persistence.codelab.db.utils.DatabaseInitializer;
+
+import java.util.List;
+import java.util.Locale;
+
+public class UsersActivity extends LifecycleActivity {
+
+    private AppDatabase mDb;
+
+    private TextView mYoungUsersTextView;
+
+    @Override
+    protected void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+
+        setContentView(R.layout.db_activity1);
+
+        mYoungUsersTextView = (TextView) findViewById(R.id.young_users_tv);
+
+        // Note: Db references should not be in an activity.
+        mDb = AppDatabase.getInMemoryDatabase(getApplicationContext());
+
+        populateDb();
+
+        fetchData();
+    }
+
+    @Override
+    protected void onDestroy() {
+        AppDatabase.destroyInstance();
+        super.onDestroy();
+    }
+
+    private void populateDb() {
+        DatabaseInitializer.populateSync(mDb);
+    }
+
+    private void fetchData() {
+        // Note: this kind of logic should not be in an activity.
+        StringBuilder sb = new StringBuilder();
+        List<User> youngUsers = mDb.userModel().findYoungerThan(35);
+        for (User youngUser : youngUsers) {
+            sb.append(String.format(Locale.US,
+                    "%s, %s (%d)\n", youngUser.lastName, youngUser.name, youngUser.age));
+        }
+        mYoungUsersTextView.setText(sb);
+    }
+}
diff --git a/samples-flatfoot/codelabs/persistence/app/src/main/java/com/example/android/persistence/codelab/step1_solution/UsersActivity.java b/samples-flatfoot/codelabs/persistence/app/src/main/java/com/example/android/persistence/codelab/step1_solution/UsersActivity.java
new file mode 100644
index 0000000..1399f0e
--- /dev/null
+++ b/samples-flatfoot/codelabs/persistence/app/src/main/java/com/example/android/persistence/codelab/step1_solution/UsersActivity.java
@@ -0,0 +1,73 @@
+/*
+ * Copyright 2017, The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES 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.persistence.codelab.step1_solution;
+
+import android.os.Bundle;
+import android.widget.TextView;
+
+import android.arch.lifecycle.LifecycleActivity;
+import com.example.android.codelabs.persistence.R;
+import com.example.android.persistence.codelab.db.AppDatabase;
+import com.example.android.persistence.codelab.db.User;
+import com.example.android.persistence.codelab.db.utils.DatabaseInitializer;
+
+import java.util.List;
+import java.util.Locale;
+
+public class UsersActivity extends LifecycleActivity {
+
+    private AppDatabase mDb;
+
+    private TextView mYoungUsersTextView;
+
+    @Override
+    protected void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+
+        setContentView(R.layout.db_activity1);
+
+        mYoungUsersTextView = (TextView) findViewById(R.id.young_users_tv);
+
+        // Note: Db references should not be in an activity.
+        mDb = AppDatabase.getInMemoryDatabase(getApplicationContext());
+
+        populateDb();
+
+        fetchData();
+    }
+
+    @Override
+    protected void onDestroy() {
+        AppDatabase.destroyInstance();
+        super.onDestroy();
+    }
+
+    private void populateDb() {
+        DatabaseInitializer.populateSync(mDb);
+    }
+
+    private void fetchData() {
+        // Note: this kind of logic should not be in an activity.
+        StringBuilder sb = new StringBuilder();
+        List<User> youngUsers = mDb.userModel().findYoungerThanSolution(35);
+        for (User youngUser : youngUsers) {
+            sb.append(String.format(Locale.US,
+                    "%s, %s (%d)\n", youngUser.lastName, youngUser.name, youngUser.age));
+        }
+        mYoungUsersTextView.setText(sb);
+    }
+}
diff --git a/samples-flatfoot/codelabs/persistence/app/src/main/java/com/example/android/persistence/codelab/step2/JankShowUserActivity.java b/samples-flatfoot/codelabs/persistence/app/src/main/java/com/example/android/persistence/codelab/step2/JankShowUserActivity.java
new file mode 100644
index 0000000..7ff52cc
--- /dev/null
+++ b/samples-flatfoot/codelabs/persistence/app/src/main/java/com/example/android/persistence/codelab/step2/JankShowUserActivity.java
@@ -0,0 +1,84 @@
+/*
+ * Copyright 2017, The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES 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.persistence.codelab.step2;
+
+import android.os.Bundle;
+import android.support.annotation.NonNull;
+import android.view.View;
+import android.widget.TextView;
+
+import android.arch.lifecycle.LifecycleActivity;
+import com.example.android.codelabs.persistence.R;
+import com.example.android.persistence.codelab.db.AppDatabase;
+import com.example.android.persistence.codelab.db.Book;
+import com.example.android.persistence.codelab.db.utils.DatabaseInitializer;
+
+import java.util.List;
+
+public class JankShowUserActivity extends LifecycleActivity {
+
+    private AppDatabase mDb;
+
+    private TextView mBooksTextView;
+
+    @Override
+    protected void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+
+        setContentView(R.layout.db_activity);
+
+        mBooksTextView = (TextView) findViewById(R.id.books_tv);
+
+        // Note: Db references should not be in an activity.
+        mDb = AppDatabase.getInMemoryDatabase(getApplicationContext());
+
+        populateDb();
+
+        fetchData();
+    }
+
+    @Override
+    protected void onDestroy() {
+        AppDatabase.destroyInstance();
+        super.onDestroy();
+    }
+
+    private void populateDb() {
+        DatabaseInitializer.populateSync(mDb);
+    }
+
+    private void fetchData() {
+        // This activity is executing a query on the main thread, making the UI perform badly.
+        List<Book> books = mDb.bookModel().findBooksBorrowedByNameSync("Mike");
+        showListInUI(books, mBooksTextView);
+    }
+
+    private static void showListInUI(final @NonNull List<Book> books,
+                                     final TextView booksTextView) {
+        StringBuilder sb = new StringBuilder();
+        for (Book book : books) {
+            sb.append(book.title);
+            sb.append("\n");
+        }
+        booksTextView.setText(sb.toString());
+    }
+
+    public void onRefreshBtClicked(View view) {
+        mBooksTextView.setText("");
+        fetchData();
+    }
+}
diff --git a/samples-flatfoot/codelabs/persistence/app/src/main/java/com/example/android/persistence/codelab/step3/BooksBorrowedByUserActivity.java b/samples-flatfoot/codelabs/persistence/app/src/main/java/com/example/android/persistence/codelab/step3/BooksBorrowedByUserActivity.java
new file mode 100644
index 0000000..0e11268
--- /dev/null
+++ b/samples-flatfoot/codelabs/persistence/app/src/main/java/com/example/android/persistence/codelab/step3/BooksBorrowedByUserActivity.java
@@ -0,0 +1,73 @@
+/*
+ * Copyright 2017, The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES 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.persistence.codelab.step3;
+
+import android.os.Bundle;
+import android.support.annotation.NonNull;
+import android.view.View;
+import android.widget.TextView;
+
+import android.arch.lifecycle.LifecycleActivity;
+import android.arch.lifecycle.ViewModelProviders;
+import com.example.android.codelabs.persistence.R;
+import com.example.android.persistence.codelab.db.Book;
+
+import java.util.List;
+
+public class BooksBorrowedByUserActivity extends LifecycleActivity {
+
+    private BooksBorrowedByUserViewModel mViewModel;
+
+    @SuppressWarnings("unused")
+    private TextView mBooksTextView;
+
+    @Override
+    protected void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+
+        setContentView(R.layout.db_activity);
+        mBooksTextView = (TextView) findViewById(R.id.books_tv);
+
+        // Get a reference to the ViewModel for this screen.
+        mViewModel = ViewModelProviders.of(this).get(BooksBorrowedByUserViewModel.class);
+
+        // Update the UI whenever there's a change in the ViewModel's data.
+        subscribeUiBooks();
+    }
+
+    public void onRefreshBtClicked(View view) {
+        mViewModel.createDb();
+    }
+
+    private void subscribeUiBooks() {
+        // TODO: refresh the list of books when there's new data
+        // mViewModel.books.observe(...
+    }
+
+    @SuppressWarnings("unused")
+    private static void showBooksInUi(final @NonNull List<Book> books,
+                                      final TextView booksTextView) {
+        StringBuilder sb = new StringBuilder();
+
+        for (Book book : books) {
+            sb.append(book.title);
+            sb.append("\n");
+
+        }
+        booksTextView.setText(sb.toString());
+    }
+}
diff --git a/samples-flatfoot/codelabs/persistence/app/src/main/java/com/example/android/persistence/codelab/step3/BooksBorrowedByUserViewModel.java b/samples-flatfoot/codelabs/persistence/app/src/main/java/com/example/android/persistence/codelab/step3/BooksBorrowedByUserViewModel.java
new file mode 100644
index 0000000..badead3
--- /dev/null
+++ b/samples-flatfoot/codelabs/persistence/app/src/main/java/com/example/android/persistence/codelab/step3/BooksBorrowedByUserViewModel.java
@@ -0,0 +1,50 @@
+/*
+ * Copyright 2017, The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES 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.persistence.codelab.step3;
+
+import android.app.Application;
+
+import android.arch.lifecycle.AndroidViewModel;
+import android.arch.lifecycle.LiveData;
+import com.example.android.persistence.codelab.db.AppDatabase;
+import com.example.android.persistence.codelab.db.Book;
+import com.example.android.persistence.codelab.db.utils.DatabaseInitializer;
+
+import java.util.List;
+
+
+public class BooksBorrowedByUserViewModel extends AndroidViewModel {
+
+    public final LiveData<List<Book>> books;
+
+    private AppDatabase mDb;
+
+    public BooksBorrowedByUserViewModel(Application application) {
+        super(application);
+        createDb();
+
+        // TODO: Assign books to the 'findBooksBorrowedByName' query.
+        books = null;
+    }
+
+    public void createDb() {
+        mDb = AppDatabase.getInMemoryDatabase(this.getApplication());
+
+        // Populate it with initial data
+        DatabaseInitializer.populateAsync(mDb);
+    }
+}
diff --git a/samples-flatfoot/codelabs/persistence/app/src/main/java/com/example/android/persistence/codelab/step3_solution/BooksBorrowedByUserActivity.java b/samples-flatfoot/codelabs/persistence/app/src/main/java/com/example/android/persistence/codelab/step3_solution/BooksBorrowedByUserActivity.java
new file mode 100644
index 0000000..d488333
--- /dev/null
+++ b/samples-flatfoot/codelabs/persistence/app/src/main/java/com/example/android/persistence/codelab/step3_solution/BooksBorrowedByUserActivity.java
@@ -0,0 +1,76 @@
+/*
+ * Copyright 2017, The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES 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.persistence.codelab.step3_solution;
+
+import android.os.Bundle;
+import android.support.annotation.NonNull;
+import android.view.View;
+import android.widget.TextView;
+
+import android.arch.lifecycle.LifecycleActivity;
+import android.arch.lifecycle.Observer;
+import android.arch.lifecycle.ViewModelProviders;
+import com.example.android.codelabs.persistence.R;
+import com.example.android.persistence.codelab.db.Book;
+
+import java.util.List;
+
+public class BooksBorrowedByUserActivity extends LifecycleActivity {
+
+    private BooksBorrowedByUserViewModel mViewModel;
+
+    private TextView mBooksTextView;
+
+    @Override
+    protected void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+
+        setContentView(R.layout.db_activity);
+        mBooksTextView = (TextView) findViewById(R.id.books_tv);
+
+        // Get a reference to the ViewModel for this screen.
+        mViewModel = ViewModelProviders.of(this).get(BooksBorrowedByUserViewModel.class);
+
+        // Update the UI whenever there's a change in the ViewModel's data.
+        subscribeUiBooks();
+    }
+
+    public void onRefreshBtClicked(View view) {
+        mViewModel.createDb();
+    }
+
+    private void subscribeUiBooks() {
+        mViewModel.books.observe(this, new Observer<List<Book>>() {
+            @Override
+            public void onChanged(@NonNull final List<Book> books) {
+                showBooksInUi(books, mBooksTextView);
+            }
+        });
+    }
+
+    private static void showBooksInUi(final @NonNull List<Book> books,
+                                      final TextView booksTextView) {
+        StringBuilder sb = new StringBuilder();
+
+        for (Book book : books) {
+            sb.append(book.title);
+            sb.append("\n");
+
+        }
+        booksTextView.setText(sb.toString());
+    }
+}
diff --git a/samples-flatfoot/codelabs/persistence/app/src/main/java/com/example/android/persistence/codelab/step3_solution/BooksBorrowedByUserViewModel.java b/samples-flatfoot/codelabs/persistence/app/src/main/java/com/example/android/persistence/codelab/step3_solution/BooksBorrowedByUserViewModel.java
new file mode 100644
index 0000000..4599b68
--- /dev/null
+++ b/samples-flatfoot/codelabs/persistence/app/src/main/java/com/example/android/persistence/codelab/step3_solution/BooksBorrowedByUserViewModel.java
@@ -0,0 +1,50 @@
+/*
+ * Copyright 2017, The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES 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.persistence.codelab.step3_solution;
+
+import android.app.Application;
+
+import android.arch.lifecycle.AndroidViewModel;
+import android.arch.lifecycle.LiveData;
+import com.example.android.persistence.codelab.db.AppDatabase;
+import com.example.android.persistence.codelab.db.Book;
+import com.example.android.persistence.codelab.db.utils.DatabaseInitializer;
+
+import java.util.List;
+
+
+public class BooksBorrowedByUserViewModel extends AndroidViewModel {
+
+    public final LiveData<List<Book>> books;
+
+    private AppDatabase mDb;
+
+    public BooksBorrowedByUserViewModel(Application application) {
+        super(application);
+        createDb();
+
+        // Books is a LiveData object so updates are observed.
+        books = mDb.bookModel().findBooksBorrowedByName("Mike");
+    }
+
+    public void createDb() {
+        mDb = AppDatabase.getInMemoryDatabase(this.getApplication());
+
+        // Populate it with initial data
+        DatabaseInitializer.populateAsync(mDb);
+    }
+}
diff --git a/samples-flatfoot/codelabs/persistence/app/src/main/java/com/example/android/persistence/codelab/step4/TypeConvertersActivity.java b/samples-flatfoot/codelabs/persistence/app/src/main/java/com/example/android/persistence/codelab/step4/TypeConvertersActivity.java
new file mode 100644
index 0000000..2af252b
--- /dev/null
+++ b/samples-flatfoot/codelabs/persistence/app/src/main/java/com/example/android/persistence/codelab/step4/TypeConvertersActivity.java
@@ -0,0 +1,76 @@
+/*
+ * Copyright 2017, The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES 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.persistence.codelab.step4;
+
+import android.os.Bundle;
+import android.support.annotation.NonNull;
+import android.view.View;
+import android.widget.TextView;
+
+import android.arch.lifecycle.LifecycleActivity;
+import android.arch.lifecycle.Observer;
+import android.arch.lifecycle.ViewModelProviders;
+import com.example.android.codelabs.persistence.R;
+import com.example.android.persistence.codelab.db.Book;
+
+import java.util.List;
+
+public class TypeConvertersActivity extends LifecycleActivity {
+
+    private TypeConvertersViewModel mViewModel;
+
+    private TextView mBooksTextView;
+
+    @Override
+    protected void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+
+        setContentView(R.layout.db_activity);
+        mBooksTextView = (TextView) findViewById(R.id.books_tv);
+
+        // Get a reference to the ViewModel for this screen.
+        mViewModel = ViewModelProviders.of(this).get(TypeConvertersViewModel.class);
+
+        // Update the UI whenever there's a change in the ViewModel's data.
+        subscribeUiBooks();
+    }
+
+    public void onRefreshBtClicked(View view) {
+        mViewModel.createDb();
+    }
+
+    private void subscribeUiBooks() {
+        mViewModel.getBooks().observe(this, new Observer<List<Book>>() {
+            @Override
+            public void onChanged(@NonNull final List<Book> books) {
+                showBooksInUi(books, mBooksTextView);
+            }
+        });
+    }
+
+    private static void showBooksInUi(final @NonNull List<Book> books,
+                                      final TextView booksTextView) {
+        StringBuilder sb = new StringBuilder();
+
+        for (Book book : books) {
+            sb.append(book.title);
+            sb.append("\n");
+
+        }
+        booksTextView.setText(sb.toString());
+    }
+}
diff --git a/samples-flatfoot/codelabs/persistence/app/src/main/java/com/example/android/persistence/codelab/step4/TypeConvertersViewModel.java b/samples-flatfoot/codelabs/persistence/app/src/main/java/com/example/android/persistence/codelab/step4/TypeConvertersViewModel.java
new file mode 100644
index 0000000..08b7edf
--- /dev/null
+++ b/samples-flatfoot/codelabs/persistence/app/src/main/java/com/example/android/persistence/codelab/step4/TypeConvertersViewModel.java
@@ -0,0 +1,60 @@
+/*
+ * Copyright 2017, The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES 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.persistence.codelab.step4;
+
+import android.app.Application;
+
+import android.arch.lifecycle.AndroidViewModel;
+import android.arch.lifecycle.LiveData;
+import com.example.android.persistence.codelab.db.AppDatabase;
+import com.example.android.persistence.codelab.db.Book;
+import com.example.android.persistence.codelab.db.utils.DatabaseInitializer;
+
+import java.util.List;
+
+
+public class TypeConvertersViewModel extends AndroidViewModel {
+
+    private LiveData<List<Book>> mBooks;
+
+    private AppDatabase mDb;
+
+    public TypeConvertersViewModel(Application application) {
+        super(application);
+        createDb();
+    }
+
+    public void createDb() {
+        mDb = AppDatabase.getInMemoryDatabase(this.getApplication());
+
+        // Populate it with initial data
+        DatabaseInitializer.populateAsync(mDb);
+
+        // Receive changes
+        subscribeToDbChanges();
+    }
+
+    public LiveData<List<Book>> getBooks() {
+        return mBooks;
+    }
+
+    private void subscribeToDbChanges() {
+        // Books is a LiveData object so updates are observed.
+        // TODO: replace this with a query that finds books borrowed by Mike in the last 24h
+        mBooks = mDb.bookModel().findBooksBorrowedByName("Mike");
+    }
+}
diff --git a/samples-flatfoot/codelabs/persistence/app/src/main/java/com/example/android/persistence/codelab/step4_solution/TypeConvertersActivity.java b/samples-flatfoot/codelabs/persistence/app/src/main/java/com/example/android/persistence/codelab/step4_solution/TypeConvertersActivity.java
new file mode 100644
index 0000000..2d309ac
--- /dev/null
+++ b/samples-flatfoot/codelabs/persistence/app/src/main/java/com/example/android/persistence/codelab/step4_solution/TypeConvertersActivity.java
@@ -0,0 +1,76 @@
+/*
+ * Copyright 2017, The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES 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.persistence.codelab.step4_solution;
+
+import android.os.Bundle;
+import android.support.annotation.NonNull;
+import android.view.View;
+import android.widget.TextView;
+
+import android.arch.lifecycle.LifecycleActivity;
+import android.arch.lifecycle.Observer;
+import android.arch.lifecycle.ViewModelProviders;
+import com.example.android.codelabs.persistence.R;
+import com.example.android.persistence.codelab.db.Book;
+
+import java.util.List;
+
+public class TypeConvertersActivity extends LifecycleActivity {
+
+    private TypeConvertersViewModel mViewModel;
+
+    private TextView mBooksTextView;
+
+    @Override
+    protected void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+
+        setContentView(R.layout.db_activity);
+        mBooksTextView = (TextView) findViewById(R.id.books_tv);
+
+        // Get a reference to the ViewModel for this screen.
+        mViewModel = ViewModelProviders.of(this).get(TypeConvertersViewModel.class);
+
+        // Update the UI whenever there's a change in the ViewModel's data.
+        subscribeUiBooks();
+    }
+
+    public void onRefreshBtClicked(View view) {
+        mViewModel.createDb();
+    }
+
+    private void subscribeUiBooks() {
+        mViewModel.getBooks().observe(this, new Observer<List<Book>>() {
+            @Override
+            public void onChanged(@NonNull final List<Book> books) {
+                showBooksInUi(books, mBooksTextView);
+            }
+        });
+    }
+
+    private static void showBooksInUi(final @NonNull List<Book> books,
+                                      final TextView booksTextView) {
+        StringBuilder sb = new StringBuilder();
+
+        for (Book book : books) {
+            sb.append(book.title);
+            sb.append("\n");
+
+        }
+        booksTextView.setText(sb.toString());
+    }
+}
diff --git a/samples-flatfoot/codelabs/persistence/app/src/main/java/com/example/android/persistence/codelab/step4_solution/TypeConvertersViewModel.java b/samples-flatfoot/codelabs/persistence/app/src/main/java/com/example/android/persistence/codelab/step4_solution/TypeConvertersViewModel.java
new file mode 100644
index 0000000..5add97c
--- /dev/null
+++ b/samples-flatfoot/codelabs/persistence/app/src/main/java/com/example/android/persistence/codelab/step4_solution/TypeConvertersViewModel.java
@@ -0,0 +1,64 @@
+/*
+ * Copyright 2017, The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES 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.persistence.codelab.step4_solution;
+
+import android.app.Application;
+
+import android.arch.lifecycle.AndroidViewModel;
+import android.arch.lifecycle.LiveData;
+import com.example.android.persistence.codelab.db.AppDatabase;
+import com.example.android.persistence.codelab.db.Book;
+import com.example.android.persistence.codelab.db.utils.DatabaseInitializer;
+
+import java.util.Calendar;
+import java.util.Date;
+import java.util.List;
+
+
+public class TypeConvertersViewModel extends AndroidViewModel {
+
+    private LiveData<List<Book>> mBooks;
+
+    private AppDatabase mDb;
+
+    public TypeConvertersViewModel(Application application) {
+        super(application);
+        createDb();
+    }
+
+    public void createDb() {
+        mDb = AppDatabase.getInMemoryDatabase(this.getApplication());
+
+        // Populate it with initial data
+        DatabaseInitializer.populateAsync(mDb);
+
+        // Receive changes
+        subscribeToDbChanges();
+    }
+
+    public LiveData<List<Book>> getBooks() {
+        return mBooks;
+    }
+
+    private void subscribeToDbChanges() {
+        // Books is a LiveData object so updates are observed.
+        Calendar calendar = Calendar.getInstance();
+        calendar.set(Calendar.DATE, -1);
+        Date yesterday = calendar.getTime();
+        mBooks = mDb.bookModel().findBooksBorrowedByNameAfter("Mike", yesterday);
+    }
+}
diff --git a/samples-flatfoot/codelabs/persistence/app/src/main/java/com/example/android/persistence/codelab/step5/CustomResultActivity.java b/samples-flatfoot/codelabs/persistence/app/src/main/java/com/example/android/persistence/codelab/step5/CustomResultActivity.java
new file mode 100644
index 0000000..2e07e97
--- /dev/null
+++ b/samples-flatfoot/codelabs/persistence/app/src/main/java/com/example/android/persistence/codelab/step5/CustomResultActivity.java
@@ -0,0 +1,67 @@
+/*
+ * Copyright 2017, The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES 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.persistence.codelab.step5;
+
+import android.os.Bundle;
+import android.support.annotation.Nullable;
+import android.view.View;
+import android.widget.TextView;
+
+import android.arch.lifecycle.LifecycleActivity;
+import android.arch.lifecycle.Observer;
+import android.arch.lifecycle.ViewModelProviders;
+import com.example.android.codelabs.persistence.R;
+
+
+public class CustomResultActivity extends LifecycleActivity {
+
+    private CustomResultViewModel mShowUserViewModel;
+
+    private TextView mBooksTextView;
+
+    @Override
+    protected void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+
+        setContentView(R.layout.db_activity);
+        mBooksTextView = (TextView) findViewById(R.id.books_tv);
+
+        mShowUserViewModel = ViewModelProviders.of(this).get(CustomResultViewModel.class);
+
+        populateDb();
+
+        subscribeUiLoans();
+    }
+
+    private void populateDb() {
+        mShowUserViewModel.createDb();
+    }
+
+    private void subscribeUiLoans() {
+        mShowUserViewModel.getLoansResult().observe(this, new Observer<String>() {
+            @Override
+            public void onChanged(@Nullable final String result) {
+                mBooksTextView.setText(result);
+            }
+        });
+    }
+
+    public void onRefreshBtClicked(View view) {
+        populateDb();
+        subscribeUiLoans();
+    }
+}
diff --git a/samples-flatfoot/codelabs/persistence/app/src/main/java/com/example/android/persistence/codelab/step5/CustomResultViewModel.java b/samples-flatfoot/codelabs/persistence/app/src/main/java/com/example/android/persistence/codelab/step5/CustomResultViewModel.java
new file mode 100644
index 0000000..157c4db
--- /dev/null
+++ b/samples-flatfoot/codelabs/persistence/app/src/main/java/com/example/android/persistence/codelab/step5/CustomResultViewModel.java
@@ -0,0 +1,90 @@
+/*
+ * Copyright 2017, The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES 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.persistence.codelab.step5;
+
+import android.app.Application;
+import android.arch.core.util.Function;
+import android.arch.lifecycle.AndroidViewModel;
+import android.arch.lifecycle.LiveData;
+import android.arch.lifecycle.Transformations;
+
+import com.example.android.persistence.codelab.db.AppDatabase;
+import com.example.android.persistence.codelab.db.LoanWithUserAndBook;
+import com.example.android.persistence.codelab.db.utils.DatabaseInitializer;
+
+import java.text.SimpleDateFormat;
+import java.util.Calendar;
+import java.util.Date;
+import java.util.List;
+import java.util.Locale;
+
+
+public class CustomResultViewModel extends AndroidViewModel {
+
+    private LiveData<String> mLoansResult;
+
+    private AppDatabase mDb;
+
+    public CustomResultViewModel(Application application) {
+        super(application);
+    }
+
+    public LiveData<String> getLoansResult() {
+        return mLoansResult;
+    }
+
+    public void createDb() {
+        mDb = AppDatabase.getInMemoryDatabase(getApplication());
+
+        // Populate it with initial data
+        DatabaseInitializer.populateAsync(mDb);
+
+        // Receive changes
+        subscribeToDbChanges();
+    }
+
+    private void subscribeToDbChanges() {
+        // TODO: Modify this query to show only recent loans from specific user
+        LiveData<List<LoanWithUserAndBook>> loans
+                = mDb.loanModel().findAllWithUserAndBook();
+
+        // Instead of exposing the list of Loans, we can apply a transformation and expose Strings.
+        mLoansResult = Transformations.map(loans,
+                new Function<List<LoanWithUserAndBook>, String>() {
+            @Override
+            public String apply(List<LoanWithUserAndBook> loansWithUserAndBook) {
+                StringBuilder sb = new StringBuilder();
+                SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm",
+                        Locale.US);
+
+                for (LoanWithUserAndBook loan : loansWithUserAndBook) {
+                    sb.append(String.format("%s\n  (Returned: %s)\n",
+                            loan.bookTitle,
+                            simpleDateFormat.format(loan.endTime)));
+                }
+                return sb.toString();
+            }
+        });
+    }
+
+    @SuppressWarnings("unused")
+    private Date getYesterdayDate() {
+        Calendar calendar = Calendar.getInstance();
+        calendar.set(Calendar.DATE, -1);
+        return calendar.getTime();
+    }
+}
diff --git a/samples-flatfoot/codelabs/persistence/app/src/main/java/com/example/android/persistence/codelab/step5_solution/CustomResultUserActivity.java b/samples-flatfoot/codelabs/persistence/app/src/main/java/com/example/android/persistence/codelab/step5_solution/CustomResultUserActivity.java
new file mode 100644
index 0000000..e17a809
--- /dev/null
+++ b/samples-flatfoot/codelabs/persistence/app/src/main/java/com/example/android/persistence/codelab/step5_solution/CustomResultUserActivity.java
@@ -0,0 +1,67 @@
+/*
+ * Copyright 2017, The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES 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.persistence.codelab.step5_solution;
+
+import android.os.Bundle;
+import android.support.annotation.Nullable;
+import android.view.View;
+import android.widget.TextView;
+
+import android.arch.lifecycle.LifecycleActivity;
+import android.arch.lifecycle.Observer;
+import android.arch.lifecycle.ViewModelProviders;
+import com.example.android.codelabs.persistence.R;
+
+
+public class CustomResultUserActivity extends LifecycleActivity {
+
+    private CustomResultViewModel mShowUserViewModel;
+
+    private TextView mBooksTextView;
+
+    @Override
+    protected void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+
+        setContentView(R.layout.db_activity);
+        mBooksTextView = (TextView) findViewById(R.id.books_tv);
+
+        mShowUserViewModel = ViewModelProviders.of(this).get(CustomResultViewModel.class);
+
+        populateDb();
+
+        subscribeUiLoans();
+    }
+
+    private void populateDb() {
+        mShowUserViewModel.createDb();
+    }
+
+    private void subscribeUiLoans() {
+        mShowUserViewModel.getLoansResult().observe(this, new Observer<String>() {
+            @Override
+            public void onChanged(@Nullable final String result) {
+                mBooksTextView.setText(result);
+            }
+        });
+    }
+
+    public void onRefreshBtClicked(View view) {
+        populateDb();
+        subscribeUiLoans();
+    }
+}
diff --git a/samples-flatfoot/codelabs/persistence/app/src/main/java/com/example/android/persistence/codelab/step5_solution/CustomResultViewModel.java b/samples-flatfoot/codelabs/persistence/app/src/main/java/com/example/android/persistence/codelab/step5_solution/CustomResultViewModel.java
new file mode 100644
index 0000000..f6f4c20
--- /dev/null
+++ b/samples-flatfoot/codelabs/persistence/app/src/main/java/com/example/android/persistence/codelab/step5_solution/CustomResultViewModel.java
@@ -0,0 +1,88 @@
+/*
+ * Copyright 2017, The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES 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.persistence.codelab.step5_solution;
+
+import android.app.Application;
+import android.arch.core.util.Function;
+import android.arch.lifecycle.AndroidViewModel;
+import android.arch.lifecycle.LiveData;
+import android.arch.lifecycle.Transformations;
+
+import com.example.android.persistence.codelab.db.AppDatabase;
+import com.example.android.persistence.codelab.db.LoanWithUserAndBook;
+import com.example.android.persistence.codelab.db.utils.DatabaseInitializer;
+
+import java.text.SimpleDateFormat;
+import java.util.Calendar;
+import java.util.Date;
+import java.util.List;
+import java.util.Locale;
+
+
+public class CustomResultViewModel extends AndroidViewModel {
+
+    private LiveData<String> mLoansResult;
+
+    private AppDatabase mDb;
+
+    public CustomResultViewModel(Application application) {
+        super(application);
+    }
+
+    public LiveData<String> getLoansResult() {
+        return mLoansResult;
+    }
+
+    public void createDb() {
+        mDb = AppDatabase.getInMemoryDatabase(getApplication());
+
+        // Populate it with initial data
+        DatabaseInitializer.populateAsync(mDb);
+
+        // Receive changes
+        subscribeToDbChanges();
+    }
+
+    private void subscribeToDbChanges() {
+        LiveData<List<LoanWithUserAndBook>> loans
+                = mDb.loanModel().findLoansByNameAfter("Mike", getYesterdayDate());
+
+        // Instead of exposing the list of Loans, we can apply a transformation and expose Strings.
+        mLoansResult = Transformations.map(loans,
+                new Function<List<LoanWithUserAndBook>, String>() {
+            @Override
+            public String apply(List<LoanWithUserAndBook> loansWithUserAndBook) {
+                StringBuilder sb = new StringBuilder();
+                SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm",
+                        Locale.US);
+
+                for (LoanWithUserAndBook loan : loansWithUserAndBook) {
+                    sb.append(String.format("%s\n  (Returned: %s)\n",
+                            loan.bookTitle,
+                            simpleDateFormat.format(loan.endTime)));
+                }
+                return sb.toString();
+            }
+        });
+    }
+
+    private Date getYesterdayDate() {
+        Calendar calendar = Calendar.getInstance();
+        calendar.set(Calendar.DATE, -1);
+        return calendar.getTime();
+    }
+}
diff --git a/samples-flatfoot/codelabs/persistence/app/src/main/res/layout/db_activity.xml b/samples-flatfoot/codelabs/persistence/app/src/main/res/layout/db_activity.xml
new file mode 100644
index 0000000..d4b7bfb
--- /dev/null
+++ b/samples-flatfoot/codelabs/persistence/app/src/main/res/layout/db_activity.xml
@@ -0,0 +1,59 @@
+<?xml version="1.0" encoding="utf-8"?><!--
+  ~ Copyright 2017, The Android Open Source Project
+  ~
+  ~ Licensed under the Apache License, Version 2.0 (the "License");
+  ~ you may not use this file except in compliance with the License.
+  ~ You may obtain a copy of the License at
+  ~
+  ~      http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing, software
+  ~ distributed under the License is distributed on an "AS IS" BASIS,
+  ~ WITHOUT 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:id="@+id/activity_main"
+    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">
+
+    <ScrollView
+        android:layout_width="match_parent"
+        android:layout_height="match_parent">
+
+        <LinearLayout
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:orientation="vertical">
+
+
+            <TextView
+                android:layout_width="match_parent"
+                android:layout_height="wrap_content"
+                android:text="@string/books_borrowed_by_user"
+                android:textSize="24sp" />
+
+            <TextView
+                android:id="@+id/books_tv"
+                android:layout_width="match_parent"
+                android:layout_height="wrap_content"
+                android:paddingStart="8dp" />
+
+            <Button
+                android:id="@+id/button"
+                android:layout_width="match_parent"
+                android:layout_height="wrap_content"
+                android:onClick="onRefreshBtClicked"
+                android:text="@string/refresh" />
+
+        </LinearLayout>
+
+    </ScrollView>
+
+</RelativeLayout>
diff --git a/samples-flatfoot/codelabs/persistence/app/src/main/res/layout/db_activity1.xml b/samples-flatfoot/codelabs/persistence/app/src/main/res/layout/db_activity1.xml
new file mode 100644
index 0000000..d603b9d
--- /dev/null
+++ b/samples-flatfoot/codelabs/persistence/app/src/main/res/layout/db_activity1.xml
@@ -0,0 +1,49 @@
+<?xml version="1.0" encoding="utf-8"?><!--
+  ~ Copyright 2017, The Android Open Source Project
+  ~
+  ~ Licensed under the Apache License, Version 2.0 (the "License");
+  ~ you may not use this file except in compliance with the License.
+  ~ You may obtain a copy of the License at
+  ~
+  ~      http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing, software
+  ~ distributed under the License is distributed on an "AS IS" BASIS,
+  ~ WITHOUT 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:id="@+id/activity_main"
+    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">
+
+    <ScrollView
+        android:layout_width="match_parent"
+        android:layout_height="match_parent">
+
+        <LinearLayout
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:orientation="vertical">
+
+            <TextView
+                android:layout_width="match_parent"
+                android:layout_height="wrap_content"
+                android:text="@string/young_users"
+                android:textSize="24sp" />
+
+            <TextView
+                android:id="@+id/young_users_tv"
+                android:layout_width="match_parent"
+                android:layout_height="wrap_content"
+                android:paddingStart="8dp" />
+
+        </LinearLayout>
+    </ScrollView>
+</RelativeLayout>
diff --git a/samples-flatfoot/codelabs/persistence/app/src/main/res/mipmap-hdpi/ic_launcher.png b/samples-flatfoot/codelabs/persistence/app/src/main/res/mipmap-hdpi/ic_launcher.png
new file mode 100644
index 0000000..e19d44f
--- /dev/null
+++ b/samples-flatfoot/codelabs/persistence/app/src/main/res/mipmap-hdpi/ic_launcher.png
Binary files differ
diff --git a/samples-flatfoot/codelabs/persistence/app/src/main/res/mipmap-mdpi/ic_launcher.png b/samples-flatfoot/codelabs/persistence/app/src/main/res/mipmap-mdpi/ic_launcher.png
new file mode 100644
index 0000000..7876250
--- /dev/null
+++ b/samples-flatfoot/codelabs/persistence/app/src/main/res/mipmap-mdpi/ic_launcher.png
Binary files differ
diff --git a/samples-flatfoot/codelabs/persistence/app/src/main/res/mipmap-xhdpi/ic_launcher.png b/samples-flatfoot/codelabs/persistence/app/src/main/res/mipmap-xhdpi/ic_launcher.png
new file mode 100644
index 0000000..9ae3725
--- /dev/null
+++ b/samples-flatfoot/codelabs/persistence/app/src/main/res/mipmap-xhdpi/ic_launcher.png
Binary files differ
diff --git a/samples-flatfoot/codelabs/persistence/app/src/main/res/mipmap-xxhdpi/ic_launcher.png b/samples-flatfoot/codelabs/persistence/app/src/main/res/mipmap-xxhdpi/ic_launcher.png
new file mode 100644
index 0000000..6a6c3aa
--- /dev/null
+++ b/samples-flatfoot/codelabs/persistence/app/src/main/res/mipmap-xxhdpi/ic_launcher.png
Binary files differ
diff --git a/samples-flatfoot/codelabs/persistence/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png b/samples-flatfoot/codelabs/persistence/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png
new file mode 100644
index 0000000..43ca523
--- /dev/null
+++ b/samples-flatfoot/codelabs/persistence/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png
Binary files differ
diff --git a/samples-flatfoot/codelabs/persistence/app/src/main/res/values/colors.xml b/samples-flatfoot/codelabs/persistence/app/src/main/res/values/colors.xml
new file mode 100644
index 0000000..255f15d
--- /dev/null
+++ b/samples-flatfoot/codelabs/persistence/app/src/main/res/values/colors.xml
@@ -0,0 +1,22 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  ~ Copyright 2017, The Android Open Source Project
+  ~
+  ~ Licensed under the Apache License, Version 2.0 (the "License");
+  ~ you may not use this file except in compliance with the License.
+  ~ You may obtain a copy of the License at
+  ~
+  ~      http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing, software
+  ~ distributed under the License is distributed on an "AS IS" BASIS,
+  ~ WITHOUT 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>
+    <color name="colorPrimary">#3F51B5</color>
+    <color name="colorPrimaryDark">#303F9F</color>
+    <color name="colorAccent">#FF4081</color>
+</resources>
diff --git a/samples-flatfoot/codelabs/persistence/app/src/main/res/values/dimens.xml b/samples-flatfoot/codelabs/persistence/app/src/main/res/values/dimens.xml
new file mode 100644
index 0000000..9a3477e
--- /dev/null
+++ b/samples-flatfoot/codelabs/persistence/app/src/main/res/values/dimens.xml
@@ -0,0 +1,21 @@
+<!--
+  ~ Copyright 2017, The Android Open Source Project
+  ~
+  ~ Licensed under the Apache License, Version 2.0 (the "License");
+  ~ you may not use this file except in compliance with the License.
+  ~ You may obtain a copy of the License at
+  ~
+  ~      http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing, software
+  ~ distributed under the License is distributed on an "AS IS" BASIS,
+  ~ WITHOUT 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>
diff --git a/samples-flatfoot/codelabs/persistence/app/src/main/res/values/strings.xml b/samples-flatfoot/codelabs/persistence/app/src/main/res/values/strings.xml
new file mode 100644
index 0000000..8d4f357
--- /dev/null
+++ b/samples-flatfoot/codelabs/persistence/app/src/main/res/values/strings.xml
@@ -0,0 +1,22 @@
+<!--
+  ~ Copyright 2017, The Android Open Source Project
+  ~
+  ~ Licensed under the Apache License, Version 2.0 (the "License");
+  ~ you may not use this file except in compliance with the License.
+  ~ You may obtain a copy of the License at
+  ~
+  ~      http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing, software
+  ~ distributed under the License is distributed on an "AS IS" BASIS,
+  ~ WITHOUT 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">Persistence codelab</string>
+    <string name="books_borrowed_by_user">Books borrowed by Mike:</string>
+    <string name="refresh">Refresh</string>
+    <string name="young_users">Young users:</string>
+</resources>
diff --git a/samples-flatfoot/codelabs/persistence/app/src/main/res/values/styles.xml b/samples-flatfoot/codelabs/persistence/app/src/main/res/values/styles.xml
new file mode 100644
index 0000000..7aa7228
--- /dev/null
+++ b/samples-flatfoot/codelabs/persistence/app/src/main/res/values/styles.xml
@@ -0,0 +1,27 @@
+<!--
+  ~ Copyright 2017, The Android Open Source Project
+  ~
+  ~ Licensed under the Apache License, Version 2.0 (the "License");
+  ~ you may not use this file except in compliance with the License.
+  ~ You may obtain a copy of the License at
+  ~
+  ~      http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing, software
+  ~ distributed under the License is distributed on an "AS IS" BASIS,
+  ~ WITHOUT 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. -->
+    <style name="AppTheme" parent="Theme.AppCompat.Light.DarkActionBar">
+        <!-- Customize your theme here. -->
+        <item name="colorPrimary">@color/colorPrimary</item>
+        <item name="colorPrimaryDark">@color/colorPrimaryDark</item>
+        <item name="colorAccent">@color/colorAccent</item>
+    </style>
+
+</resources>
diff --git a/samples-flatfoot/codelabs/persistence/build.gradle b/samples-flatfoot/codelabs/persistence/build.gradle
new file mode 100644
index 0000000..c68afa8
--- /dev/null
+++ b/samples-flatfoot/codelabs/persistence/build.gradle
@@ -0,0 +1,52 @@
+/*
+ * Copyright 2017, The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT 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 build file where you can add configuration options common to all sub-projects/modules.
+
+buildscript {
+    repositories {
+        jcenter()
+    }
+    dependencies {
+        classpath 'com.android.tools.build:gradle:2.3.0'
+
+        // NOTE: Do not place your application dependencies here; they belong
+        // in the individual module build.gradle files
+    }
+}
+
+allprojects {
+    repositories {
+        jcenter()
+        maven {
+            url "../../../prebuilts0905"
+        }
+    }
+}
+
+task clean(type: Delete) {
+    delete rootProject.buildDir
+}
+
+ext {
+    buildToolsVersion = "25.0.2"
+    supportLibVersion = "25.3.1"
+    runnerVersion = "0.5"
+    rulesVersion = "0.5"
+    espressoVersion = "2.2.2"
+    archLifecycleVersion = "1.0-SNAPSHOT"
+    archRoomVersion = "1.0-SNAPSHOT"
+}
\ No newline at end of file
diff --git a/samples-flatfoot/codelabs/persistence/gradle.properties b/samples-flatfoot/codelabs/persistence/gradle.properties
new file mode 100644
index 0000000..684bee6
--- /dev/null
+++ b/samples-flatfoot/codelabs/persistence/gradle.properties
@@ -0,0 +1,33 @@
+#
+# Copyright 2017, The Android Open Source Project
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#      http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+
+# Project-wide Gradle settings.
+
+# IDE (e.g. Android Studio) users:
+# Gradle settings configured through the IDE *will override*
+# any settings specified in this file.
+
+# For more details on how to configure your build environment visit
+# http://www.gradle.org/docs/current/userguide/build_environment.html
+
+# Specifies the JVM arguments used for the daemon process.
+# The setting is particularly useful for tweaking memory settings.
+org.gradle.jvmargs=-Xmx1536m
+
+# When configured, Gradle will run in incubating parallel mode.
+# This option should only be used with decoupled projects. More details, visit
+# http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects
+# org.gradle.parallel=true
diff --git a/samples-flatfoot/codelabs/persistence/gradle/wrapper/gradle-wrapper.jar b/samples-flatfoot/codelabs/persistence/gradle/wrapper/gradle-wrapper.jar
new file mode 100644
index 0000000..13372ae
--- /dev/null
+++ b/samples-flatfoot/codelabs/persistence/gradle/wrapper/gradle-wrapper.jar
Binary files differ
diff --git a/samples-flatfoot/codelabs/persistence/gradle/wrapper/gradle-wrapper.properties b/samples-flatfoot/codelabs/persistence/gradle/wrapper/gradle-wrapper.properties
new file mode 100644
index 0000000..1af3d91
--- /dev/null
+++ b/samples-flatfoot/codelabs/persistence/gradle/wrapper/gradle-wrapper.properties
@@ -0,0 +1,22 @@
+#
+# Copyright 2017, The Android Open Source Project
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#      http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+
+#Mon Feb 20 17:11:12 GMT 2017
+distributionBase=GRADLE_USER_HOME
+distributionPath=wrapper/dists
+zipStoreBase=GRADLE_USER_HOME
+zipStorePath=wrapper/dists
+distributionUrl=https\://services.gradle.org/distributions/gradle-3.3-all.zip
diff --git a/samples-flatfoot/codelabs/persistence/gradlew b/samples-flatfoot/codelabs/persistence/gradlew
new file mode 100755
index 0000000..9d82f78
--- /dev/null
+++ b/samples-flatfoot/codelabs/persistence/gradlew
@@ -0,0 +1,160 @@
+#!/usr/bin/env bash
+
+##############################################################################
+##
+##  Gradle start up script for UN*X
+##
+##############################################################################
+
+# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
+DEFAULT_JVM_OPTS=""
+
+APP_NAME="Gradle"
+APP_BASE_NAME=`basename "$0"`
+
+# Use the maximum available, or set MAX_FD != -1 to use that value.
+MAX_FD="maximum"
+
+warn ( ) {
+    echo "$*"
+}
+
+die ( ) {
+    echo
+    echo "$*"
+    echo
+    exit 1
+}
+
+# OS specific support (must be 'true' or 'false').
+cygwin=false
+msys=false
+darwin=false
+case "`uname`" in
+  CYGWIN* )
+    cygwin=true
+    ;;
+  Darwin* )
+    darwin=true
+    ;;
+  MINGW* )
+    msys=true
+    ;;
+esac
+
+# Attempt to set APP_HOME
+# Resolve links: $0 may be a link
+PRG="$0"
+# Need this for relative symlinks.
+while [ -h "$PRG" ] ; do
+    ls=`ls -ld "$PRG"`
+    link=`expr "$ls" : '.*-> \(.*\)$'`
+    if expr "$link" : '/.*' > /dev/null; then
+        PRG="$link"
+    else
+        PRG=`dirname "$PRG"`"/$link"
+    fi
+done
+SAVED="`pwd`"
+cd "`dirname \"$PRG\"`/" >/dev/null
+APP_HOME="`pwd -P`"
+cd "$SAVED" >/dev/null
+
+CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
+
+# Determine the Java command to use to start the JVM.
+if [ -n "$JAVA_HOME" ] ; then
+    if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
+        # IBM's JDK on AIX uses strange locations for the executables
+        JAVACMD="$JAVA_HOME/jre/sh/java"
+    else
+        JAVACMD="$JAVA_HOME/bin/java"
+    fi
+    if [ ! -x "$JAVACMD" ] ; then
+        die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
+
+Please set the JAVA_HOME variable in your environment to match the
+location of your Java installation."
+    fi
+else
+    JAVACMD="java"
+    which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
+
+Please set the JAVA_HOME variable in your environment to match the
+location of your Java installation."
+fi
+
+# Increase the maximum file descriptors if we can.
+if [ "$cygwin" = "false" -a "$darwin" = "false" ] ; then
+    MAX_FD_LIMIT=`ulimit -H -n`
+    if [ $? -eq 0 ] ; then
+        if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then
+            MAX_FD="$MAX_FD_LIMIT"
+        fi
+        ulimit -n $MAX_FD
+        if [ $? -ne 0 ] ; then
+            warn "Could not set maximum file descriptor limit: $MAX_FD"
+        fi
+    else
+        warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT"
+    fi
+fi
+
+# For Darwin, add options to specify how the application appears in the dock
+if $darwin; then
+    GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\""
+fi
+
+# For Cygwin, switch paths to Windows format before running java
+if $cygwin ; then
+    APP_HOME=`cygpath --path --mixed "$APP_HOME"`
+    CLASSPATH=`cygpath --path --mixed "$CLASSPATH"`
+    JAVACMD=`cygpath --unix "$JAVACMD"`
+
+    # We build the pattern for arguments to be converted via cygpath
+    ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null`
+    SEP=""
+    for dir in $ROOTDIRSRAW ; do
+        ROOTDIRS="$ROOTDIRS$SEP$dir"
+        SEP="|"
+    done
+    OURCYGPATTERN="(^($ROOTDIRS))"
+    # Add a user-defined pattern to the cygpath arguments
+    if [ "$GRADLE_CYGPATTERN" != "" ] ; then
+        OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)"
+    fi
+    # Now convert the arguments - kludge to limit ourselves to /bin/sh
+    i=0
+    for arg in "$@" ; do
+        CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -`
+        CHECK2=`echo "$arg"|egrep -c "^-"`                                 ### Determine if an option
+
+        if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then                    ### Added a condition
+            eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"`
+        else
+            eval `echo args$i`="\"$arg\""
+        fi
+        i=$((i+1))
+    done
+    case $i in
+        (0) set -- ;;
+        (1) set -- "$args0" ;;
+        (2) set -- "$args0" "$args1" ;;
+        (3) set -- "$args0" "$args1" "$args2" ;;
+        (4) set -- "$args0" "$args1" "$args2" "$args3" ;;
+        (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;;
+        (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;;
+        (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;;
+        (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;;
+        (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;;
+    esac
+fi
+
+# Split up the JVM_OPTS And GRADLE_OPTS values into an array, following the shell quoting and substitution rules
+function splitJvmOpts() {
+    JVM_OPTS=("$@")
+}
+eval splitJvmOpts $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS
+JVM_OPTS[${#JVM_OPTS[*]}]="-Dorg.gradle.appname=$APP_BASE_NAME"
+
+exec "$JAVACMD" "${JVM_OPTS[@]}" -classpath "$CLASSPATH" org.gradle.wrapper.GradleWrapperMain "$@"
diff --git a/samples-flatfoot/codelabs/persistence/gradlew.bat b/samples-flatfoot/codelabs/persistence/gradlew.bat
new file mode 100644
index 0000000..aec9973
--- /dev/null
+++ b/samples-flatfoot/codelabs/persistence/gradlew.bat
@@ -0,0 +1,90 @@
+@if "%DEBUG%" == "" @echo off

+@rem ##########################################################################

+@rem

+@rem  Gradle startup script for Windows

+@rem

+@rem ##########################################################################

+

+@rem Set local scope for the variables with windows NT shell

+if "%OS%"=="Windows_NT" setlocal

+

+@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.

+set DEFAULT_JVM_OPTS=

+

+set DIRNAME=%~dp0

+if "%DIRNAME%" == "" set DIRNAME=.

+set APP_BASE_NAME=%~n0

+set APP_HOME=%DIRNAME%

+

+@rem Find java.exe

+if defined JAVA_HOME goto findJavaFromJavaHome

+

+set JAVA_EXE=java.exe

+%JAVA_EXE% -version >NUL 2>&1

+if "%ERRORLEVEL%" == "0" goto init

+

+echo.

+echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.

+echo.

+echo Please set the JAVA_HOME variable in your environment to match the

+echo location of your Java installation.

+

+goto fail

+

+:findJavaFromJavaHome

+set JAVA_HOME=%JAVA_HOME:"=%

+set JAVA_EXE=%JAVA_HOME%/bin/java.exe

+

+if exist "%JAVA_EXE%" goto init

+

+echo.

+echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%

+echo.

+echo Please set the JAVA_HOME variable in your environment to match the

+echo location of your Java installation.

+

+goto fail

+

+:init

+@rem Get command-line arguments, handling Windowz variants

+

+if not "%OS%" == "Windows_NT" goto win9xME_args

+if "%@eval[2+2]" == "4" goto 4NT_args

+

+:win9xME_args

+@rem Slurp the command line arguments.

+set CMD_LINE_ARGS=

+set _SKIP=2

+

+:win9xME_args_slurp

+if "x%~1" == "x" goto execute

+

+set CMD_LINE_ARGS=%*

+goto execute

+

+:4NT_args

+@rem Get arguments from the 4NT Shell from JP Software

+set CMD_LINE_ARGS=%$

+

+:execute

+@rem Setup the command line

+

+set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar

+

+@rem Execute Gradle

+"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS%

+

+:end

+@rem End local scope for the variables with windows NT shell

+if "%ERRORLEVEL%"=="0" goto mainEnd

+

+:fail

+rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of

+rem the _cmd.exe /c_ return code!

+if  not "" == "%GRADLE_EXIT_CONSOLE%" exit 1

+exit /b 1

+

+:mainEnd

+if "%OS%"=="Windows_NT" endlocal

+

+:omega

diff --git a/samples-flatfoot/codelabs/persistence/settings.gradle b/samples-flatfoot/codelabs/persistence/settings.gradle
new file mode 100644
index 0000000..a266f7d
--- /dev/null
+++ b/samples-flatfoot/codelabs/persistence/settings.gradle
@@ -0,0 +1,17 @@
+/*
+ * Copyright 2017, The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+include ':app'
diff --git a/samples-flatfoot/prebuilts0905/android/arch/core/core-testing/1.0-SNAPSHOT/core-testing-1.0-20170509.055323-1-sources.jar b/samples-flatfoot/prebuilts0905/android/arch/core/core-testing/1.0-SNAPSHOT/core-testing-1.0-20170509.055323-1-sources.jar
new file mode 100644
index 0000000..6138a05
--- /dev/null
+++ b/samples-flatfoot/prebuilts0905/android/arch/core/core-testing/1.0-SNAPSHOT/core-testing-1.0-20170509.055323-1-sources.jar
Binary files differ
diff --git a/samples-flatfoot/prebuilts0905/android/arch/core/core-testing/1.0-SNAPSHOT/core-testing-1.0-20170509.055323-1-sources.jar.md5 b/samples-flatfoot/prebuilts0905/android/arch/core/core-testing/1.0-SNAPSHOT/core-testing-1.0-20170509.055323-1-sources.jar.md5
new file mode 100644
index 0000000..21687b1
--- /dev/null
+++ b/samples-flatfoot/prebuilts0905/android/arch/core/core-testing/1.0-SNAPSHOT/core-testing-1.0-20170509.055323-1-sources.jar.md5
@@ -0,0 +1 @@
+ca1aba09bd76b2d712a70b09674fa412
\ No newline at end of file
diff --git a/samples-flatfoot/prebuilts0905/android/arch/core/core-testing/1.0-SNAPSHOT/core-testing-1.0-20170509.055323-1-sources.jar.sha1 b/samples-flatfoot/prebuilts0905/android/arch/core/core-testing/1.0-SNAPSHOT/core-testing-1.0-20170509.055323-1-sources.jar.sha1
new file mode 100644
index 0000000..c355fa9
--- /dev/null
+++ b/samples-flatfoot/prebuilts0905/android/arch/core/core-testing/1.0-SNAPSHOT/core-testing-1.0-20170509.055323-1-sources.jar.sha1
@@ -0,0 +1 @@
+786fff12d11854e99079f3e0e6b83ebd28ff760b
\ No newline at end of file
diff --git a/samples-flatfoot/prebuilts0905/android/arch/core/core-testing/1.0-SNAPSHOT/core-testing-1.0-20170509.055323-1.aar b/samples-flatfoot/prebuilts0905/android/arch/core/core-testing/1.0-SNAPSHOT/core-testing-1.0-20170509.055323-1.aar
new file mode 100644
index 0000000..841ae62
--- /dev/null
+++ b/samples-flatfoot/prebuilts0905/android/arch/core/core-testing/1.0-SNAPSHOT/core-testing-1.0-20170509.055323-1.aar
Binary files differ
diff --git a/samples-flatfoot/prebuilts0905/android/arch/core/core-testing/1.0-SNAPSHOT/core-testing-1.0-20170509.055323-1.aar.md5 b/samples-flatfoot/prebuilts0905/android/arch/core/core-testing/1.0-SNAPSHOT/core-testing-1.0-20170509.055323-1.aar.md5
new file mode 100644
index 0000000..900ce94
--- /dev/null
+++ b/samples-flatfoot/prebuilts0905/android/arch/core/core-testing/1.0-SNAPSHOT/core-testing-1.0-20170509.055323-1.aar.md5
@@ -0,0 +1 @@
+9e86f87a007df4935a8cf0082e1320a9
\ No newline at end of file
diff --git a/samples-flatfoot/prebuilts0905/android/arch/core/core-testing/1.0-SNAPSHOT/core-testing-1.0-20170509.055323-1.aar.sha1 b/samples-flatfoot/prebuilts0905/android/arch/core/core-testing/1.0-SNAPSHOT/core-testing-1.0-20170509.055323-1.aar.sha1
new file mode 100644
index 0000000..eb644c7
--- /dev/null
+++ b/samples-flatfoot/prebuilts0905/android/arch/core/core-testing/1.0-SNAPSHOT/core-testing-1.0-20170509.055323-1.aar.sha1
@@ -0,0 +1 @@
+3b31c3725f3657796df063e274b23e6383d662db
\ No newline at end of file
diff --git a/samples-flatfoot/prebuilts0905/android/arch/core/core-testing/1.0-SNAPSHOT/core-testing-1.0-20170509.055323-1.pom b/samples-flatfoot/prebuilts0905/android/arch/core/core-testing/1.0-SNAPSHOT/core-testing-1.0-20170509.055323-1.pom
new file mode 100644
index 0000000..bc302ff
--- /dev/null
+++ b/samples-flatfoot/prebuilts0905/android/arch/core/core-testing/1.0-SNAPSHOT/core-testing-1.0-20170509.055323-1.pom
@@ -0,0 +1,47 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd" xmlns="http://maven.apache.org/POM/4.0.0"
+    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
+  <modelVersion>4.0.0</modelVersion>
+  <groupId>android.arch.core</groupId>
+  <artifactId>core-testing</artifactId>
+  <version>1.0-SNAPSHOT</version>
+  <packaging>aar</packaging>
+  <dependencies>
+    <dependency>
+      <groupId>android.arch.core</groupId>
+      <artifactId>core</artifactId>
+      <version>1.0-SNAPSHOT</version>
+      <scope>compile</scope>
+    </dependency>
+    <dependency>
+      <groupId>com.android.support</groupId>
+      <artifactId>support-annotations</artifactId>
+      <version>25.2.0</version>
+      <scope>compile</scope>
+    </dependency>
+    <dependency>
+      <groupId>com.android.support</groupId>
+      <artifactId>support-core-utils</artifactId>
+      <version>25.2.0</version>
+      <scope>compile</scope>
+    </dependency>
+    <dependency>
+      <groupId>junit</groupId>
+      <artifactId>junit</artifactId>
+      <version>4.12</version>
+      <scope>compile</scope>
+      <exclusions>
+        <exclusion>
+          <artifactId>hamcrest-core</artifactId>
+          <groupId>*</groupId>
+        </exclusion>
+      </exclusions>
+    </dependency>
+    <dependency>
+      <groupId>org.mockito</groupId>
+      <artifactId>mockito-core</artifactId>
+      <version>1.9.5</version>
+      <scope>compile</scope>
+    </dependency>
+  </dependencies>
+</project>
diff --git a/samples-flatfoot/prebuilts0905/android/arch/core/core-testing/1.0-SNAPSHOT/core-testing-1.0-20170509.055323-1.pom.md5 b/samples-flatfoot/prebuilts0905/android/arch/core/core-testing/1.0-SNAPSHOT/core-testing-1.0-20170509.055323-1.pom.md5
new file mode 100644
index 0000000..99a13b1
--- /dev/null
+++ b/samples-flatfoot/prebuilts0905/android/arch/core/core-testing/1.0-SNAPSHOT/core-testing-1.0-20170509.055323-1.pom.md5
@@ -0,0 +1 @@
+9161bbbd4308131850c1620bb771413b
\ No newline at end of file
diff --git a/samples-flatfoot/prebuilts0905/android/arch/core/core-testing/1.0-SNAPSHOT/core-testing-1.0-20170509.055323-1.pom.sha1 b/samples-flatfoot/prebuilts0905/android/arch/core/core-testing/1.0-SNAPSHOT/core-testing-1.0-20170509.055323-1.pom.sha1
new file mode 100644
index 0000000..8881d77
--- /dev/null
+++ b/samples-flatfoot/prebuilts0905/android/arch/core/core-testing/1.0-SNAPSHOT/core-testing-1.0-20170509.055323-1.pom.sha1
@@ -0,0 +1 @@
+980c8177656c28f493cb217a9062edc76f3d3200
\ No newline at end of file
diff --git a/samples-flatfoot/prebuilts0905/android/arch/core/core-testing/1.0-SNAPSHOT/maven-metadata.xml b/samples-flatfoot/prebuilts0905/android/arch/core/core-testing/1.0-SNAPSHOT/maven-metadata.xml
new file mode 100644
index 0000000..3f48496
--- /dev/null
+++ b/samples-flatfoot/prebuilts0905/android/arch/core/core-testing/1.0-SNAPSHOT/maven-metadata.xml
@@ -0,0 +1,13 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<metadata>
+  <groupId>android.arch.core</groupId>
+  <artifactId>core-testing</artifactId>
+  <version>1.0-SNAPSHOT</version>
+  <versioning>
+    <snapshot>
+      <timestamp>20170509.055323</timestamp>
+      <buildNumber>1</buildNumber>
+    </snapshot>
+    <lastUpdated>20170509055323</lastUpdated>
+  </versioning>
+</metadata>
diff --git a/samples-flatfoot/prebuilts0905/android/arch/core/core-testing/1.0-SNAPSHOT/maven-metadata.xml.md5 b/samples-flatfoot/prebuilts0905/android/arch/core/core-testing/1.0-SNAPSHOT/maven-metadata.xml.md5
new file mode 100644
index 0000000..482731c
--- /dev/null
+++ b/samples-flatfoot/prebuilts0905/android/arch/core/core-testing/1.0-SNAPSHOT/maven-metadata.xml.md5
@@ -0,0 +1 @@
+16f7cf4f43258b77ea8e33338cd1f4cb
\ No newline at end of file
diff --git a/samples-flatfoot/prebuilts0905/android/arch/core/core-testing/1.0-SNAPSHOT/maven-metadata.xml.sha1 b/samples-flatfoot/prebuilts0905/android/arch/core/core-testing/1.0-SNAPSHOT/maven-metadata.xml.sha1
new file mode 100644
index 0000000..a01b80b
--- /dev/null
+++ b/samples-flatfoot/prebuilts0905/android/arch/core/core-testing/1.0-SNAPSHOT/maven-metadata.xml.sha1
@@ -0,0 +1 @@
+d7c7da1de4e1b50b126834001789e48c480cec9b
\ No newline at end of file
diff --git a/samples-flatfoot/prebuilts0905/android/arch/core/core-testing/maven-metadata.xml b/samples-flatfoot/prebuilts0905/android/arch/core/core-testing/maven-metadata.xml
new file mode 100644
index 0000000..bdb88ee
--- /dev/null
+++ b/samples-flatfoot/prebuilts0905/android/arch/core/core-testing/maven-metadata.xml
@@ -0,0 +1,11 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<metadata>
+  <groupId>android.arch.core</groupId>
+  <artifactId>core-testing</artifactId>
+  <versioning>
+    <versions>
+      <version>1.0-SNAPSHOT</version>
+    </versions>
+    <lastUpdated>20170509055323</lastUpdated>
+  </versioning>
+</metadata>
diff --git a/samples-flatfoot/prebuilts0905/android/arch/core/core-testing/maven-metadata.xml.md5 b/samples-flatfoot/prebuilts0905/android/arch/core/core-testing/maven-metadata.xml.md5
new file mode 100644
index 0000000..2bbeb72
--- /dev/null
+++ b/samples-flatfoot/prebuilts0905/android/arch/core/core-testing/maven-metadata.xml.md5
@@ -0,0 +1 @@
+28ca42527f6d8bec31b416b80d6ffc54
\ No newline at end of file
diff --git a/samples-flatfoot/prebuilts0905/android/arch/core/core-testing/maven-metadata.xml.sha1 b/samples-flatfoot/prebuilts0905/android/arch/core/core-testing/maven-metadata.xml.sha1
new file mode 100644
index 0000000..cd44769
--- /dev/null
+++ b/samples-flatfoot/prebuilts0905/android/arch/core/core-testing/maven-metadata.xml.sha1
@@ -0,0 +1 @@
+94753c43775c32019a4f85863e1338d7ff3638aa
\ No newline at end of file
diff --git a/samples-flatfoot/prebuilts0905/android/arch/core/core/1.0-SNAPSHOT/core-1.0-20170509.055320-1-sources.jar b/samples-flatfoot/prebuilts0905/android/arch/core/core/1.0-SNAPSHOT/core-1.0-20170509.055320-1-sources.jar
new file mode 100644
index 0000000..f52dc52
--- /dev/null
+++ b/samples-flatfoot/prebuilts0905/android/arch/core/core/1.0-SNAPSHOT/core-1.0-20170509.055320-1-sources.jar
Binary files differ
diff --git a/samples-flatfoot/prebuilts0905/android/arch/core/core/1.0-SNAPSHOT/core-1.0-20170509.055320-1-sources.jar.md5 b/samples-flatfoot/prebuilts0905/android/arch/core/core/1.0-SNAPSHOT/core-1.0-20170509.055320-1-sources.jar.md5
new file mode 100644
index 0000000..2de86ff
--- /dev/null
+++ b/samples-flatfoot/prebuilts0905/android/arch/core/core/1.0-SNAPSHOT/core-1.0-20170509.055320-1-sources.jar.md5
@@ -0,0 +1 @@
+a9cb0e2a06be3862ac3eb5dd0a40bc38
\ No newline at end of file
diff --git a/samples-flatfoot/prebuilts0905/android/arch/core/core/1.0-SNAPSHOT/core-1.0-20170509.055320-1-sources.jar.sha1 b/samples-flatfoot/prebuilts0905/android/arch/core/core/1.0-SNAPSHOT/core-1.0-20170509.055320-1-sources.jar.sha1
new file mode 100644
index 0000000..fff6c6e
--- /dev/null
+++ b/samples-flatfoot/prebuilts0905/android/arch/core/core/1.0-SNAPSHOT/core-1.0-20170509.055320-1-sources.jar.sha1
@@ -0,0 +1 @@
+492b289a5504fa4911396b6aa30f51152f3bd63d
\ No newline at end of file
diff --git a/samples-flatfoot/prebuilts0905/android/arch/core/core/1.0-SNAPSHOT/core-1.0-20170509.055320-1.aar b/samples-flatfoot/prebuilts0905/android/arch/core/core/1.0-SNAPSHOT/core-1.0-20170509.055320-1.aar
new file mode 100644
index 0000000..f7f1fe2
--- /dev/null
+++ b/samples-flatfoot/prebuilts0905/android/arch/core/core/1.0-SNAPSHOT/core-1.0-20170509.055320-1.aar
Binary files differ
diff --git a/samples-flatfoot/prebuilts0905/android/arch/core/core/1.0-SNAPSHOT/core-1.0-20170509.055320-1.aar.md5 b/samples-flatfoot/prebuilts0905/android/arch/core/core/1.0-SNAPSHOT/core-1.0-20170509.055320-1.aar.md5
new file mode 100644
index 0000000..5e269c4
--- /dev/null
+++ b/samples-flatfoot/prebuilts0905/android/arch/core/core/1.0-SNAPSHOT/core-1.0-20170509.055320-1.aar.md5
@@ -0,0 +1 @@
+85e796de2d30eb59d8efab529494aef1
\ No newline at end of file
diff --git a/samples-flatfoot/prebuilts0905/android/arch/core/core/1.0-SNAPSHOT/core-1.0-20170509.055320-1.aar.sha1 b/samples-flatfoot/prebuilts0905/android/arch/core/core/1.0-SNAPSHOT/core-1.0-20170509.055320-1.aar.sha1
new file mode 100644
index 0000000..50b923f
--- /dev/null
+++ b/samples-flatfoot/prebuilts0905/android/arch/core/core/1.0-SNAPSHOT/core-1.0-20170509.055320-1.aar.sha1
@@ -0,0 +1 @@
+9b8ae95c6cc663e2d878fa0eb8d5929d48e317be
\ No newline at end of file
diff --git a/samples-flatfoot/prebuilts0905/android/arch/core/core/1.0-SNAPSHOT/core-1.0-20170509.055320-1.pom b/samples-flatfoot/prebuilts0905/android/arch/core/core/1.0-SNAPSHOT/core-1.0-20170509.055320-1.pom
new file mode 100644
index 0000000..e4f8577
--- /dev/null
+++ b/samples-flatfoot/prebuilts0905/android/arch/core/core/1.0-SNAPSHOT/core-1.0-20170509.055320-1.pom
@@ -0,0 +1,23 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd" xmlns="http://maven.apache.org/POM/4.0.0"
+    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
+  <modelVersion>4.0.0</modelVersion>
+  <groupId>android.arch.core</groupId>
+  <artifactId>core</artifactId>
+  <version>1.0-SNAPSHOT</version>
+  <packaging>aar</packaging>
+  <dependencies>
+    <dependency>
+      <groupId>com.android.support</groupId>
+      <artifactId>support-annotations</artifactId>
+      <version>25.2.0</version>
+      <scope>compile</scope>
+    </dependency>
+    <dependency>
+      <groupId>com.android.support</groupId>
+      <artifactId>support-core-utils</artifactId>
+      <version>25.2.0</version>
+      <scope>compile</scope>
+    </dependency>
+  </dependencies>
+</project>
diff --git a/samples-flatfoot/prebuilts0905/android/arch/core/core/1.0-SNAPSHOT/core-1.0-20170509.055320-1.pom.md5 b/samples-flatfoot/prebuilts0905/android/arch/core/core/1.0-SNAPSHOT/core-1.0-20170509.055320-1.pom.md5
new file mode 100644
index 0000000..f3b5050
--- /dev/null
+++ b/samples-flatfoot/prebuilts0905/android/arch/core/core/1.0-SNAPSHOT/core-1.0-20170509.055320-1.pom.md5
@@ -0,0 +1 @@
+50b9fc578d98dbde38da151dd8b255ab
\ No newline at end of file
diff --git a/samples-flatfoot/prebuilts0905/android/arch/core/core/1.0-SNAPSHOT/core-1.0-20170509.055320-1.pom.sha1 b/samples-flatfoot/prebuilts0905/android/arch/core/core/1.0-SNAPSHOT/core-1.0-20170509.055320-1.pom.sha1
new file mode 100644
index 0000000..91cc482
--- /dev/null
+++ b/samples-flatfoot/prebuilts0905/android/arch/core/core/1.0-SNAPSHOT/core-1.0-20170509.055320-1.pom.sha1
@@ -0,0 +1 @@
+9f6fb7815cbc0182e9ec7f8265a1cdb7b61683d6
\ No newline at end of file
diff --git a/samples-flatfoot/prebuilts0905/android/arch/core/core/1.0-SNAPSHOT/maven-metadata.xml b/samples-flatfoot/prebuilts0905/android/arch/core/core/1.0-SNAPSHOT/maven-metadata.xml
new file mode 100644
index 0000000..200b060
--- /dev/null
+++ b/samples-flatfoot/prebuilts0905/android/arch/core/core/1.0-SNAPSHOT/maven-metadata.xml
@@ -0,0 +1,13 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<metadata>
+  <groupId>android.arch.core</groupId>
+  <artifactId>core</artifactId>
+  <version>1.0-SNAPSHOT</version>
+  <versioning>
+    <snapshot>
+      <timestamp>20170509.055320</timestamp>
+      <buildNumber>1</buildNumber>
+    </snapshot>
+    <lastUpdated>20170509055320</lastUpdated>
+  </versioning>
+</metadata>
diff --git a/samples-flatfoot/prebuilts0905/android/arch/core/core/1.0-SNAPSHOT/maven-metadata.xml.md5 b/samples-flatfoot/prebuilts0905/android/arch/core/core/1.0-SNAPSHOT/maven-metadata.xml.md5
new file mode 100644
index 0000000..e5613e2
--- /dev/null
+++ b/samples-flatfoot/prebuilts0905/android/arch/core/core/1.0-SNAPSHOT/maven-metadata.xml.md5
@@ -0,0 +1 @@
+3c01bf68e8d32b635a83b8253fc25d23
\ No newline at end of file
diff --git a/samples-flatfoot/prebuilts0905/android/arch/core/core/1.0-SNAPSHOT/maven-metadata.xml.sha1 b/samples-flatfoot/prebuilts0905/android/arch/core/core/1.0-SNAPSHOT/maven-metadata.xml.sha1
new file mode 100644
index 0000000..b26628c
--- /dev/null
+++ b/samples-flatfoot/prebuilts0905/android/arch/core/core/1.0-SNAPSHOT/maven-metadata.xml.sha1
@@ -0,0 +1 @@
+58c742a83ad30c448a9a761d47c294c6cc8ac7c1
\ No newline at end of file
diff --git a/samples-flatfoot/prebuilts0905/android/arch/core/core/maven-metadata.xml b/samples-flatfoot/prebuilts0905/android/arch/core/core/maven-metadata.xml
new file mode 100644
index 0000000..239807f
--- /dev/null
+++ b/samples-flatfoot/prebuilts0905/android/arch/core/core/maven-metadata.xml
@@ -0,0 +1,11 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<metadata>
+  <groupId>android.arch.core</groupId>
+  <artifactId>core</artifactId>
+  <versioning>
+    <versions>
+      <version>1.0-SNAPSHOT</version>
+    </versions>
+    <lastUpdated>20170509055320</lastUpdated>
+  </versioning>
+</metadata>
diff --git a/samples-flatfoot/prebuilts0905/android/arch/core/core/maven-metadata.xml.md5 b/samples-flatfoot/prebuilts0905/android/arch/core/core/maven-metadata.xml.md5
new file mode 100644
index 0000000..fa46ec3
--- /dev/null
+++ b/samples-flatfoot/prebuilts0905/android/arch/core/core/maven-metadata.xml.md5
@@ -0,0 +1 @@
+60274a3697bb7af7269caf05465128b0
\ No newline at end of file
diff --git a/samples-flatfoot/prebuilts0905/android/arch/core/core/maven-metadata.xml.sha1 b/samples-flatfoot/prebuilts0905/android/arch/core/core/maven-metadata.xml.sha1
new file mode 100644
index 0000000..d2982c8
--- /dev/null
+++ b/samples-flatfoot/prebuilts0905/android/arch/core/core/maven-metadata.xml.sha1
@@ -0,0 +1 @@
+519a49c6d42f3b1c626f50ac403a9d41eabec4a6
\ No newline at end of file
diff --git a/samples-flatfoot/prebuilts0905/android/arch/lifecycle/common/1.0-SNAPSHOT/common-1.0-20170509.055319-1-sources.jar b/samples-flatfoot/prebuilts0905/android/arch/lifecycle/common/1.0-SNAPSHOT/common-1.0-20170509.055319-1-sources.jar
new file mode 100644
index 0000000..e40c05a
--- /dev/null
+++ b/samples-flatfoot/prebuilts0905/android/arch/lifecycle/common/1.0-SNAPSHOT/common-1.0-20170509.055319-1-sources.jar
Binary files differ
diff --git a/samples-flatfoot/prebuilts0905/android/arch/lifecycle/common/1.0-SNAPSHOT/common-1.0-20170509.055319-1-sources.jar.md5 b/samples-flatfoot/prebuilts0905/android/arch/lifecycle/common/1.0-SNAPSHOT/common-1.0-20170509.055319-1-sources.jar.md5
new file mode 100644
index 0000000..a0a52da
--- /dev/null
+++ b/samples-flatfoot/prebuilts0905/android/arch/lifecycle/common/1.0-SNAPSHOT/common-1.0-20170509.055319-1-sources.jar.md5
@@ -0,0 +1 @@
+e8baf35acac6b25d983783be3e621cda
\ No newline at end of file
diff --git a/samples-flatfoot/prebuilts0905/android/arch/lifecycle/common/1.0-SNAPSHOT/common-1.0-20170509.055319-1-sources.jar.sha1 b/samples-flatfoot/prebuilts0905/android/arch/lifecycle/common/1.0-SNAPSHOT/common-1.0-20170509.055319-1-sources.jar.sha1
new file mode 100644
index 0000000..58a271e
--- /dev/null
+++ b/samples-flatfoot/prebuilts0905/android/arch/lifecycle/common/1.0-SNAPSHOT/common-1.0-20170509.055319-1-sources.jar.sha1
@@ -0,0 +1 @@
+89fb66a8f95627748ead98b767a49049ffde0dff
\ No newline at end of file
diff --git a/samples-flatfoot/prebuilts0905/android/arch/lifecycle/common/1.0-SNAPSHOT/common-1.0-20170509.055319-1.jar b/samples-flatfoot/prebuilts0905/android/arch/lifecycle/common/1.0-SNAPSHOT/common-1.0-20170509.055319-1.jar
new file mode 100644
index 0000000..4aa7230
--- /dev/null
+++ b/samples-flatfoot/prebuilts0905/android/arch/lifecycle/common/1.0-SNAPSHOT/common-1.0-20170509.055319-1.jar
Binary files differ
diff --git a/samples-flatfoot/prebuilts0905/android/arch/lifecycle/common/1.0-SNAPSHOT/common-1.0-20170509.055319-1.jar.md5 b/samples-flatfoot/prebuilts0905/android/arch/lifecycle/common/1.0-SNAPSHOT/common-1.0-20170509.055319-1.jar.md5
new file mode 100644
index 0000000..68484a7
--- /dev/null
+++ b/samples-flatfoot/prebuilts0905/android/arch/lifecycle/common/1.0-SNAPSHOT/common-1.0-20170509.055319-1.jar.md5
@@ -0,0 +1 @@
+802e82041a07652f61f9165b153dc15d
\ No newline at end of file
diff --git a/samples-flatfoot/prebuilts0905/android/arch/lifecycle/common/1.0-SNAPSHOT/common-1.0-20170509.055319-1.jar.sha1 b/samples-flatfoot/prebuilts0905/android/arch/lifecycle/common/1.0-SNAPSHOT/common-1.0-20170509.055319-1.jar.sha1
new file mode 100644
index 0000000..f83bdc4
--- /dev/null
+++ b/samples-flatfoot/prebuilts0905/android/arch/lifecycle/common/1.0-SNAPSHOT/common-1.0-20170509.055319-1.jar.sha1
@@ -0,0 +1 @@
+e3145ca81e2c53e03fd28e2055038c6dcbd685a3
\ No newline at end of file
diff --git a/samples-flatfoot/prebuilts0905/android/arch/lifecycle/common/1.0-SNAPSHOT/common-1.0-20170509.055319-1.pom b/samples-flatfoot/prebuilts0905/android/arch/lifecycle/common/1.0-SNAPSHOT/common-1.0-20170509.055319-1.pom
new file mode 100644
index 0000000..2a2c6a2
--- /dev/null
+++ b/samples-flatfoot/prebuilts0905/android/arch/lifecycle/common/1.0-SNAPSHOT/common-1.0-20170509.055319-1.pom
@@ -0,0 +1,28 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd" xmlns="http://maven.apache.org/POM/4.0.0"
+    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
+  <modelVersion>4.0.0</modelVersion>
+  <groupId>android.arch.lifecycle</groupId>
+  <artifactId>common</artifactId>
+  <version>1.0-SNAPSHOT</version>
+  <dependencies>
+    <dependency>
+      <groupId>com.android.support</groupId>
+      <artifactId>support-annotations</artifactId>
+      <version>25.2.0</version>
+      <scope>compile</scope>
+    </dependency>
+    <dependency>
+      <groupId>junit</groupId>
+      <artifactId>junit</artifactId>
+      <version>4.12</version>
+      <scope>test</scope>
+    </dependency>
+    <dependency>
+      <groupId>org.mockito</groupId>
+      <artifactId>mockito-core</artifactId>
+      <version>1.9.5</version>
+      <scope>test</scope>
+    </dependency>
+  </dependencies>
+</project>
diff --git a/samples-flatfoot/prebuilts0905/android/arch/lifecycle/common/1.0-SNAPSHOT/common-1.0-20170509.055319-1.pom.md5 b/samples-flatfoot/prebuilts0905/android/arch/lifecycle/common/1.0-SNAPSHOT/common-1.0-20170509.055319-1.pom.md5
new file mode 100644
index 0000000..f587d0d
--- /dev/null
+++ b/samples-flatfoot/prebuilts0905/android/arch/lifecycle/common/1.0-SNAPSHOT/common-1.0-20170509.055319-1.pom.md5
@@ -0,0 +1 @@
+a3d47b73dc2c60f4e284edccaa32a2c6
\ No newline at end of file
diff --git a/samples-flatfoot/prebuilts0905/android/arch/lifecycle/common/1.0-SNAPSHOT/common-1.0-20170509.055319-1.pom.sha1 b/samples-flatfoot/prebuilts0905/android/arch/lifecycle/common/1.0-SNAPSHOT/common-1.0-20170509.055319-1.pom.sha1
new file mode 100644
index 0000000..4b27254
--- /dev/null
+++ b/samples-flatfoot/prebuilts0905/android/arch/lifecycle/common/1.0-SNAPSHOT/common-1.0-20170509.055319-1.pom.sha1
@@ -0,0 +1 @@
+08b48078fcd3405b228d765b3f5330657177dcf9
\ No newline at end of file
diff --git a/samples-flatfoot/prebuilts0905/android/arch/lifecycle/common/1.0-SNAPSHOT/maven-metadata.xml b/samples-flatfoot/prebuilts0905/android/arch/lifecycle/common/1.0-SNAPSHOT/maven-metadata.xml
new file mode 100644
index 0000000..0639952
--- /dev/null
+++ b/samples-flatfoot/prebuilts0905/android/arch/lifecycle/common/1.0-SNAPSHOT/maven-metadata.xml
@@ -0,0 +1,13 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<metadata>
+  <groupId>android.arch.lifecycle</groupId>
+  <artifactId>common</artifactId>
+  <version>1.0-SNAPSHOT</version>
+  <versioning>
+    <snapshot>
+      <timestamp>20170509.055319</timestamp>
+      <buildNumber>1</buildNumber>
+    </snapshot>
+    <lastUpdated>20170509055319</lastUpdated>
+  </versioning>
+</metadata>
diff --git a/samples-flatfoot/prebuilts0905/android/arch/lifecycle/common/1.0-SNAPSHOT/maven-metadata.xml.md5 b/samples-flatfoot/prebuilts0905/android/arch/lifecycle/common/1.0-SNAPSHOT/maven-metadata.xml.md5
new file mode 100644
index 0000000..de4dbeb
--- /dev/null
+++ b/samples-flatfoot/prebuilts0905/android/arch/lifecycle/common/1.0-SNAPSHOT/maven-metadata.xml.md5
@@ -0,0 +1 @@
+adfb440ef27599c81051adf3c6870fdd
\ No newline at end of file
diff --git a/samples-flatfoot/prebuilts0905/android/arch/lifecycle/common/1.0-SNAPSHOT/maven-metadata.xml.sha1 b/samples-flatfoot/prebuilts0905/android/arch/lifecycle/common/1.0-SNAPSHOT/maven-metadata.xml.sha1
new file mode 100644
index 0000000..2cdc1f1
--- /dev/null
+++ b/samples-flatfoot/prebuilts0905/android/arch/lifecycle/common/1.0-SNAPSHOT/maven-metadata.xml.sha1
@@ -0,0 +1 @@
+fe97e167517cf6a5dea2542bdb6dcf0a8b4fd836
\ No newline at end of file
diff --git a/samples-flatfoot/prebuilts0905/android/arch/lifecycle/common/maven-metadata.xml b/samples-flatfoot/prebuilts0905/android/arch/lifecycle/common/maven-metadata.xml
new file mode 100644
index 0000000..d57f771
--- /dev/null
+++ b/samples-flatfoot/prebuilts0905/android/arch/lifecycle/common/maven-metadata.xml
@@ -0,0 +1,11 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<metadata>
+  <groupId>android.arch.lifecycle</groupId>
+  <artifactId>common</artifactId>
+  <versioning>
+    <versions>
+      <version>1.0-SNAPSHOT</version>
+    </versions>
+    <lastUpdated>20170509055319</lastUpdated>
+  </versioning>
+</metadata>
diff --git a/samples-flatfoot/prebuilts0905/android/arch/lifecycle/common/maven-metadata.xml.md5 b/samples-flatfoot/prebuilts0905/android/arch/lifecycle/common/maven-metadata.xml.md5
new file mode 100644
index 0000000..0028096
--- /dev/null
+++ b/samples-flatfoot/prebuilts0905/android/arch/lifecycle/common/maven-metadata.xml.md5
@@ -0,0 +1 @@
+9e7b509d54a18ba3dd9d4490c5e0fe4b
\ No newline at end of file
diff --git a/samples-flatfoot/prebuilts0905/android/arch/lifecycle/common/maven-metadata.xml.sha1 b/samples-flatfoot/prebuilts0905/android/arch/lifecycle/common/maven-metadata.xml.sha1
new file mode 100644
index 0000000..6a4a338
--- /dev/null
+++ b/samples-flatfoot/prebuilts0905/android/arch/lifecycle/common/maven-metadata.xml.sha1
@@ -0,0 +1 @@
+0a2cc2cde711fcfab4a2130f23a2ffc46da1c9e2
\ No newline at end of file
diff --git a/samples-flatfoot/prebuilts0905/android/arch/lifecycle/compiler/1.0-SNAPSHOT/compiler-1.0-20170509.055334-1.jar b/samples-flatfoot/prebuilts0905/android/arch/lifecycle/compiler/1.0-SNAPSHOT/compiler-1.0-20170509.055334-1.jar
new file mode 100644
index 0000000..c4ec5ee
--- /dev/null
+++ b/samples-flatfoot/prebuilts0905/android/arch/lifecycle/compiler/1.0-SNAPSHOT/compiler-1.0-20170509.055334-1.jar
Binary files differ
diff --git a/samples-flatfoot/prebuilts0905/android/arch/lifecycle/compiler/1.0-SNAPSHOT/compiler-1.0-20170509.055334-1.jar.md5 b/samples-flatfoot/prebuilts0905/android/arch/lifecycle/compiler/1.0-SNAPSHOT/compiler-1.0-20170509.055334-1.jar.md5
new file mode 100644
index 0000000..5f9022d
--- /dev/null
+++ b/samples-flatfoot/prebuilts0905/android/arch/lifecycle/compiler/1.0-SNAPSHOT/compiler-1.0-20170509.055334-1.jar.md5
@@ -0,0 +1 @@
+283d1cef886ab31cdfa36fc4f4723522
\ No newline at end of file
diff --git a/samples-flatfoot/prebuilts0905/android/arch/lifecycle/compiler/1.0-SNAPSHOT/compiler-1.0-20170509.055334-1.jar.sha1 b/samples-flatfoot/prebuilts0905/android/arch/lifecycle/compiler/1.0-SNAPSHOT/compiler-1.0-20170509.055334-1.jar.sha1
new file mode 100644
index 0000000..057382f
--- /dev/null
+++ b/samples-flatfoot/prebuilts0905/android/arch/lifecycle/compiler/1.0-SNAPSHOT/compiler-1.0-20170509.055334-1.jar.sha1
@@ -0,0 +1 @@
+bb04fae46a4f5b3437647c6b220d3f83e743a142
\ No newline at end of file
diff --git a/samples-flatfoot/prebuilts0905/android/arch/lifecycle/compiler/1.0-SNAPSHOT/compiler-1.0-20170509.055334-1.pom b/samples-flatfoot/prebuilts0905/android/arch/lifecycle/compiler/1.0-SNAPSHOT/compiler-1.0-20170509.055334-1.pom
new file mode 100644
index 0000000..f535b27
--- /dev/null
+++ b/samples-flatfoot/prebuilts0905/android/arch/lifecycle/compiler/1.0-SNAPSHOT/compiler-1.0-20170509.055334-1.pom
@@ -0,0 +1,40 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd" xmlns="http://maven.apache.org/POM/4.0.0"
+    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
+  <modelVersion>4.0.0</modelVersion>
+  <groupId>android.arch.lifecycle</groupId>
+  <artifactId>compiler</artifactId>
+  <version>1.0-SNAPSHOT</version>
+  <dependencies>
+    <dependency>
+      <groupId>android.arch.lifecycle</groupId>
+      <artifactId>common</artifactId>
+      <version>1.0-SNAPSHOT</version>
+      <scope>compile</scope>
+    </dependency>
+    <dependency>
+      <groupId>org.jetbrains.kotlin</groupId>
+      <artifactId>kotlin-stdlib</artifactId>
+      <version>1.1.1</version>
+      <scope>compile</scope>
+    </dependency>
+    <dependency>
+      <groupId>com.google.auto</groupId>
+      <artifactId>auto-common</artifactId>
+      <version>0.6</version>
+      <scope>compile</scope>
+    </dependency>
+    <dependency>
+      <groupId>com.squareup</groupId>
+      <artifactId>javapoet</artifactId>
+      <version>1.8.0</version>
+      <scope>compile</scope>
+    </dependency>
+    <dependency>
+      <groupId>com.google.testing.compile</groupId>
+      <artifactId>compile-testing</artifactId>
+      <version>0.9</version>
+      <scope>test</scope>
+    </dependency>
+  </dependencies>
+</project>
diff --git a/samples-flatfoot/prebuilts0905/android/arch/lifecycle/compiler/1.0-SNAPSHOT/compiler-1.0-20170509.055334-1.pom.md5 b/samples-flatfoot/prebuilts0905/android/arch/lifecycle/compiler/1.0-SNAPSHOT/compiler-1.0-20170509.055334-1.pom.md5
new file mode 100644
index 0000000..6133381
--- /dev/null
+++ b/samples-flatfoot/prebuilts0905/android/arch/lifecycle/compiler/1.0-SNAPSHOT/compiler-1.0-20170509.055334-1.pom.md5
@@ -0,0 +1 @@
+11e01c4576a32a85e2217ace258bb3ae
\ No newline at end of file
diff --git a/samples-flatfoot/prebuilts0905/android/arch/lifecycle/compiler/1.0-SNAPSHOT/compiler-1.0-20170509.055334-1.pom.sha1 b/samples-flatfoot/prebuilts0905/android/arch/lifecycle/compiler/1.0-SNAPSHOT/compiler-1.0-20170509.055334-1.pom.sha1
new file mode 100644
index 0000000..7f95c9e
--- /dev/null
+++ b/samples-flatfoot/prebuilts0905/android/arch/lifecycle/compiler/1.0-SNAPSHOT/compiler-1.0-20170509.055334-1.pom.sha1
@@ -0,0 +1 @@
+0b05cdc5ef9399c719781af8eca2181e26c497eb
\ No newline at end of file
diff --git a/samples-flatfoot/prebuilts0905/android/arch/lifecycle/compiler/1.0-SNAPSHOT/maven-metadata.xml b/samples-flatfoot/prebuilts0905/android/arch/lifecycle/compiler/1.0-SNAPSHOT/maven-metadata.xml
new file mode 100644
index 0000000..9f84bdd
--- /dev/null
+++ b/samples-flatfoot/prebuilts0905/android/arch/lifecycle/compiler/1.0-SNAPSHOT/maven-metadata.xml
@@ -0,0 +1,13 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<metadata>
+  <groupId>android.arch.lifecycle</groupId>
+  <artifactId>compiler</artifactId>
+  <version>1.0-SNAPSHOT</version>
+  <versioning>
+    <snapshot>
+      <timestamp>20170509.055334</timestamp>
+      <buildNumber>1</buildNumber>
+    </snapshot>
+    <lastUpdated>20170509055334</lastUpdated>
+  </versioning>
+</metadata>
diff --git a/samples-flatfoot/prebuilts0905/android/arch/lifecycle/compiler/1.0-SNAPSHOT/maven-metadata.xml.md5 b/samples-flatfoot/prebuilts0905/android/arch/lifecycle/compiler/1.0-SNAPSHOT/maven-metadata.xml.md5
new file mode 100644
index 0000000..42056ce
--- /dev/null
+++ b/samples-flatfoot/prebuilts0905/android/arch/lifecycle/compiler/1.0-SNAPSHOT/maven-metadata.xml.md5
@@ -0,0 +1 @@
+a8acb146e14626b29f5fcadaf0024440
\ No newline at end of file
diff --git a/samples-flatfoot/prebuilts0905/android/arch/lifecycle/compiler/1.0-SNAPSHOT/maven-metadata.xml.sha1 b/samples-flatfoot/prebuilts0905/android/arch/lifecycle/compiler/1.0-SNAPSHOT/maven-metadata.xml.sha1
new file mode 100644
index 0000000..63adc5c
--- /dev/null
+++ b/samples-flatfoot/prebuilts0905/android/arch/lifecycle/compiler/1.0-SNAPSHOT/maven-metadata.xml.sha1
@@ -0,0 +1 @@
+4f276e57a746f8db4893e08f0f27ae8ba56d7fce
\ No newline at end of file
diff --git a/samples-flatfoot/prebuilts0905/android/arch/lifecycle/compiler/maven-metadata.xml b/samples-flatfoot/prebuilts0905/android/arch/lifecycle/compiler/maven-metadata.xml
new file mode 100644
index 0000000..d5436f7
--- /dev/null
+++ b/samples-flatfoot/prebuilts0905/android/arch/lifecycle/compiler/maven-metadata.xml
@@ -0,0 +1,11 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<metadata>
+  <groupId>android.arch.lifecycle</groupId>
+  <artifactId>compiler</artifactId>
+  <versioning>
+    <versions>
+      <version>1.0-SNAPSHOT</version>
+    </versions>
+    <lastUpdated>20170509055334</lastUpdated>
+  </versioning>
+</metadata>
diff --git a/samples-flatfoot/prebuilts0905/android/arch/lifecycle/compiler/maven-metadata.xml.md5 b/samples-flatfoot/prebuilts0905/android/arch/lifecycle/compiler/maven-metadata.xml.md5
new file mode 100644
index 0000000..f76a96f
--- /dev/null
+++ b/samples-flatfoot/prebuilts0905/android/arch/lifecycle/compiler/maven-metadata.xml.md5
@@ -0,0 +1 @@
+c57843ea079fc099a5154241eec684cf
\ No newline at end of file
diff --git a/samples-flatfoot/prebuilts0905/android/arch/lifecycle/compiler/maven-metadata.xml.sha1 b/samples-flatfoot/prebuilts0905/android/arch/lifecycle/compiler/maven-metadata.xml.sha1
new file mode 100644
index 0000000..f9a9353
--- /dev/null
+++ b/samples-flatfoot/prebuilts0905/android/arch/lifecycle/compiler/maven-metadata.xml.sha1
@@ -0,0 +1 @@
+5fc8da2188394ab4e2eb58c531c9220f13db8a24
\ No newline at end of file
diff --git a/samples-flatfoot/prebuilts0905/android/arch/lifecycle/extensions/1.0-SNAPSHOT/extensions-1.0-20170509.055326-1-sources.jar b/samples-flatfoot/prebuilts0905/android/arch/lifecycle/extensions/1.0-SNAPSHOT/extensions-1.0-20170509.055326-1-sources.jar
new file mode 100644
index 0000000..d73e7df
--- /dev/null
+++ b/samples-flatfoot/prebuilts0905/android/arch/lifecycle/extensions/1.0-SNAPSHOT/extensions-1.0-20170509.055326-1-sources.jar
Binary files differ
diff --git a/samples-flatfoot/prebuilts0905/android/arch/lifecycle/extensions/1.0-SNAPSHOT/extensions-1.0-20170509.055326-1-sources.jar.md5 b/samples-flatfoot/prebuilts0905/android/arch/lifecycle/extensions/1.0-SNAPSHOT/extensions-1.0-20170509.055326-1-sources.jar.md5
new file mode 100644
index 0000000..767e164
--- /dev/null
+++ b/samples-flatfoot/prebuilts0905/android/arch/lifecycle/extensions/1.0-SNAPSHOT/extensions-1.0-20170509.055326-1-sources.jar.md5
@@ -0,0 +1 @@
+e4b150782bee7fb260a2c78853477add
\ No newline at end of file
diff --git a/samples-flatfoot/prebuilts0905/android/arch/lifecycle/extensions/1.0-SNAPSHOT/extensions-1.0-20170509.055326-1-sources.jar.sha1 b/samples-flatfoot/prebuilts0905/android/arch/lifecycle/extensions/1.0-SNAPSHOT/extensions-1.0-20170509.055326-1-sources.jar.sha1
new file mode 100644
index 0000000..bd310fa
--- /dev/null
+++ b/samples-flatfoot/prebuilts0905/android/arch/lifecycle/extensions/1.0-SNAPSHOT/extensions-1.0-20170509.055326-1-sources.jar.sha1
@@ -0,0 +1 @@
+a5925ed0438d698a075e6414436cfdcc4c9e0ba7
\ No newline at end of file
diff --git a/samples-flatfoot/prebuilts0905/android/arch/lifecycle/extensions/1.0-SNAPSHOT/extensions-1.0-20170509.055326-1.aar b/samples-flatfoot/prebuilts0905/android/arch/lifecycle/extensions/1.0-SNAPSHOT/extensions-1.0-20170509.055326-1.aar
new file mode 100644
index 0000000..a09f22c
--- /dev/null
+++ b/samples-flatfoot/prebuilts0905/android/arch/lifecycle/extensions/1.0-SNAPSHOT/extensions-1.0-20170509.055326-1.aar
Binary files differ
diff --git a/samples-flatfoot/prebuilts0905/android/arch/lifecycle/extensions/1.0-SNAPSHOT/extensions-1.0-20170509.055326-1.aar.md5 b/samples-flatfoot/prebuilts0905/android/arch/lifecycle/extensions/1.0-SNAPSHOT/extensions-1.0-20170509.055326-1.aar.md5
new file mode 100644
index 0000000..89fe675
--- /dev/null
+++ b/samples-flatfoot/prebuilts0905/android/arch/lifecycle/extensions/1.0-SNAPSHOT/extensions-1.0-20170509.055326-1.aar.md5
@@ -0,0 +1 @@
+e418844524d5c65029aa348c53a0bf67
\ No newline at end of file
diff --git a/samples-flatfoot/prebuilts0905/android/arch/lifecycle/extensions/1.0-SNAPSHOT/extensions-1.0-20170509.055326-1.aar.sha1 b/samples-flatfoot/prebuilts0905/android/arch/lifecycle/extensions/1.0-SNAPSHOT/extensions-1.0-20170509.055326-1.aar.sha1
new file mode 100644
index 0000000..d39825b
--- /dev/null
+++ b/samples-flatfoot/prebuilts0905/android/arch/lifecycle/extensions/1.0-SNAPSHOT/extensions-1.0-20170509.055326-1.aar.sha1
@@ -0,0 +1 @@
+373647ae3a3bad83e032c612649efd2ea133799e
\ No newline at end of file
diff --git a/samples-flatfoot/prebuilts0905/android/arch/lifecycle/extensions/1.0-SNAPSHOT/extensions-1.0-20170509.055326-1.pom b/samples-flatfoot/prebuilts0905/android/arch/lifecycle/extensions/1.0-SNAPSHOT/extensions-1.0-20170509.055326-1.pom
new file mode 100644
index 0000000..9296ff2
--- /dev/null
+++ b/samples-flatfoot/prebuilts0905/android/arch/lifecycle/extensions/1.0-SNAPSHOT/extensions-1.0-20170509.055326-1.pom
@@ -0,0 +1,35 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd" xmlns="http://maven.apache.org/POM/4.0.0"
+    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
+  <modelVersion>4.0.0</modelVersion>
+  <groupId>android.arch.lifecycle</groupId>
+  <artifactId>extensions</artifactId>
+  <version>1.0-SNAPSHOT</version>
+  <packaging>aar</packaging>
+  <dependencies>
+    <dependency>
+      <groupId>android.arch.lifecycle</groupId>
+      <artifactId>common</artifactId>
+      <version>1.0-SNAPSHOT</version>
+      <scope>compile</scope>
+    </dependency>
+    <dependency>
+      <groupId>android.arch.lifecycle</groupId>
+      <artifactId>runtime</artifactId>
+      <version>1.0-SNAPSHOT</version>
+      <scope>compile</scope>
+    </dependency>
+    <dependency>
+      <groupId>android.arch.core</groupId>
+      <artifactId>core</artifactId>
+      <version>1.0-SNAPSHOT</version>
+      <scope>compile</scope>
+    </dependency>
+    <dependency>
+      <groupId>com.android.support</groupId>
+      <artifactId>support-fragment</artifactId>
+      <version>25.2.0</version>
+      <scope>compile</scope>
+    </dependency>
+  </dependencies>
+</project>
diff --git a/samples-flatfoot/prebuilts0905/android/arch/lifecycle/extensions/1.0-SNAPSHOT/extensions-1.0-20170509.055326-1.pom.md5 b/samples-flatfoot/prebuilts0905/android/arch/lifecycle/extensions/1.0-SNAPSHOT/extensions-1.0-20170509.055326-1.pom.md5
new file mode 100644
index 0000000..7978412
--- /dev/null
+++ b/samples-flatfoot/prebuilts0905/android/arch/lifecycle/extensions/1.0-SNAPSHOT/extensions-1.0-20170509.055326-1.pom.md5
@@ -0,0 +1 @@
+b3f086ec40beb1fcc4bdbe38c5a20991
\ No newline at end of file
diff --git a/samples-flatfoot/prebuilts0905/android/arch/lifecycle/extensions/1.0-SNAPSHOT/extensions-1.0-20170509.055326-1.pom.sha1 b/samples-flatfoot/prebuilts0905/android/arch/lifecycle/extensions/1.0-SNAPSHOT/extensions-1.0-20170509.055326-1.pom.sha1
new file mode 100644
index 0000000..0dfcc82
--- /dev/null
+++ b/samples-flatfoot/prebuilts0905/android/arch/lifecycle/extensions/1.0-SNAPSHOT/extensions-1.0-20170509.055326-1.pom.sha1
@@ -0,0 +1 @@
+1b0f4dacffaededdf8586e0725ee7b885a35ca78
\ No newline at end of file
diff --git a/samples-flatfoot/prebuilts0905/android/arch/lifecycle/extensions/1.0-SNAPSHOT/maven-metadata.xml b/samples-flatfoot/prebuilts0905/android/arch/lifecycle/extensions/1.0-SNAPSHOT/maven-metadata.xml
new file mode 100644
index 0000000..af54226
--- /dev/null
+++ b/samples-flatfoot/prebuilts0905/android/arch/lifecycle/extensions/1.0-SNAPSHOT/maven-metadata.xml
@@ -0,0 +1,13 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<metadata>
+  <groupId>android.arch.lifecycle</groupId>
+  <artifactId>extensions</artifactId>
+  <version>1.0-SNAPSHOT</version>
+  <versioning>
+    <snapshot>
+      <timestamp>20170509.055326</timestamp>
+      <buildNumber>1</buildNumber>
+    </snapshot>
+    <lastUpdated>20170509055326</lastUpdated>
+  </versioning>
+</metadata>
diff --git a/samples-flatfoot/prebuilts0905/android/arch/lifecycle/extensions/1.0-SNAPSHOT/maven-metadata.xml.md5 b/samples-flatfoot/prebuilts0905/android/arch/lifecycle/extensions/1.0-SNAPSHOT/maven-metadata.xml.md5
new file mode 100644
index 0000000..efc9595
--- /dev/null
+++ b/samples-flatfoot/prebuilts0905/android/arch/lifecycle/extensions/1.0-SNAPSHOT/maven-metadata.xml.md5
@@ -0,0 +1 @@
+99b5c18f18ea47c80ece0f2491238a30
\ No newline at end of file
diff --git a/samples-flatfoot/prebuilts0905/android/arch/lifecycle/extensions/1.0-SNAPSHOT/maven-metadata.xml.sha1 b/samples-flatfoot/prebuilts0905/android/arch/lifecycle/extensions/1.0-SNAPSHOT/maven-metadata.xml.sha1
new file mode 100644
index 0000000..2898c42
--- /dev/null
+++ b/samples-flatfoot/prebuilts0905/android/arch/lifecycle/extensions/1.0-SNAPSHOT/maven-metadata.xml.sha1
@@ -0,0 +1 @@
+0dfc0c4ec791e73736f931cc858dedc97b3fd7f3
\ No newline at end of file
diff --git a/samples-flatfoot/prebuilts0905/android/arch/lifecycle/extensions/maven-metadata.xml b/samples-flatfoot/prebuilts0905/android/arch/lifecycle/extensions/maven-metadata.xml
new file mode 100644
index 0000000..93ce52b
--- /dev/null
+++ b/samples-flatfoot/prebuilts0905/android/arch/lifecycle/extensions/maven-metadata.xml
@@ -0,0 +1,11 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<metadata>
+  <groupId>android.arch.lifecycle</groupId>
+  <artifactId>extensions</artifactId>
+  <versioning>
+    <versions>
+      <version>1.0-SNAPSHOT</version>
+    </versions>
+    <lastUpdated>20170509055326</lastUpdated>
+  </versioning>
+</metadata>
diff --git a/samples-flatfoot/prebuilts0905/android/arch/lifecycle/extensions/maven-metadata.xml.md5 b/samples-flatfoot/prebuilts0905/android/arch/lifecycle/extensions/maven-metadata.xml.md5
new file mode 100644
index 0000000..522068b
--- /dev/null
+++ b/samples-flatfoot/prebuilts0905/android/arch/lifecycle/extensions/maven-metadata.xml.md5
@@ -0,0 +1 @@
+040c8b66d0d04d6c22a3387168a3ea55
\ No newline at end of file
diff --git a/samples-flatfoot/prebuilts0905/android/arch/lifecycle/extensions/maven-metadata.xml.sha1 b/samples-flatfoot/prebuilts0905/android/arch/lifecycle/extensions/maven-metadata.xml.sha1
new file mode 100644
index 0000000..60128e1
--- /dev/null
+++ b/samples-flatfoot/prebuilts0905/android/arch/lifecycle/extensions/maven-metadata.xml.sha1
@@ -0,0 +1 @@
+bb7352424e9431928663f02b3c0601594851185d
\ No newline at end of file
diff --git a/samples-flatfoot/prebuilts0905/android/arch/lifecycle/reactivestreams/1.0-SNAPSHOT/maven-metadata.xml b/samples-flatfoot/prebuilts0905/android/arch/lifecycle/reactivestreams/1.0-SNAPSHOT/maven-metadata.xml
new file mode 100644
index 0000000..74ef725
--- /dev/null
+++ b/samples-flatfoot/prebuilts0905/android/arch/lifecycle/reactivestreams/1.0-SNAPSHOT/maven-metadata.xml
@@ -0,0 +1,13 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<metadata>
+  <groupId>android.arch.lifecycle</groupId>
+  <artifactId>reactivestreams</artifactId>
+  <version>1.0-SNAPSHOT</version>
+  <versioning>
+    <snapshot>
+      <timestamp>20170509.055326</timestamp>
+      <buildNumber>1</buildNumber>
+    </snapshot>
+    <lastUpdated>20170509055326</lastUpdated>
+  </versioning>
+</metadata>
diff --git a/samples-flatfoot/prebuilts0905/android/arch/lifecycle/reactivestreams/1.0-SNAPSHOT/maven-metadata.xml.md5 b/samples-flatfoot/prebuilts0905/android/arch/lifecycle/reactivestreams/1.0-SNAPSHOT/maven-metadata.xml.md5
new file mode 100644
index 0000000..933c1d0
--- /dev/null
+++ b/samples-flatfoot/prebuilts0905/android/arch/lifecycle/reactivestreams/1.0-SNAPSHOT/maven-metadata.xml.md5
@@ -0,0 +1 @@
+7fc056a05fe5d7c22a1e7272a29b3260
\ No newline at end of file
diff --git a/samples-flatfoot/prebuilts0905/android/arch/lifecycle/reactivestreams/1.0-SNAPSHOT/maven-metadata.xml.sha1 b/samples-flatfoot/prebuilts0905/android/arch/lifecycle/reactivestreams/1.0-SNAPSHOT/maven-metadata.xml.sha1
new file mode 100644
index 0000000..dee724e
--- /dev/null
+++ b/samples-flatfoot/prebuilts0905/android/arch/lifecycle/reactivestreams/1.0-SNAPSHOT/maven-metadata.xml.sha1
@@ -0,0 +1 @@
+1a0545cbc716124ace898ea8d8591682fba09858
\ No newline at end of file
diff --git a/samples-flatfoot/prebuilts0905/android/arch/lifecycle/reactivestreams/1.0-SNAPSHOT/reactivestreams-1.0-20170509.055326-1-sources.jar b/samples-flatfoot/prebuilts0905/android/arch/lifecycle/reactivestreams/1.0-SNAPSHOT/reactivestreams-1.0-20170509.055326-1-sources.jar
new file mode 100644
index 0000000..fdf77cb
--- /dev/null
+++ b/samples-flatfoot/prebuilts0905/android/arch/lifecycle/reactivestreams/1.0-SNAPSHOT/reactivestreams-1.0-20170509.055326-1-sources.jar
Binary files differ
diff --git a/samples-flatfoot/prebuilts0905/android/arch/lifecycle/reactivestreams/1.0-SNAPSHOT/reactivestreams-1.0-20170509.055326-1-sources.jar.md5 b/samples-flatfoot/prebuilts0905/android/arch/lifecycle/reactivestreams/1.0-SNAPSHOT/reactivestreams-1.0-20170509.055326-1-sources.jar.md5
new file mode 100644
index 0000000..10ec5e4
--- /dev/null
+++ b/samples-flatfoot/prebuilts0905/android/arch/lifecycle/reactivestreams/1.0-SNAPSHOT/reactivestreams-1.0-20170509.055326-1-sources.jar.md5
@@ -0,0 +1 @@
+52a8b0601fcc93dbe00c6c4383a761f9
\ No newline at end of file
diff --git a/samples-flatfoot/prebuilts0905/android/arch/lifecycle/reactivestreams/1.0-SNAPSHOT/reactivestreams-1.0-20170509.055326-1-sources.jar.sha1 b/samples-flatfoot/prebuilts0905/android/arch/lifecycle/reactivestreams/1.0-SNAPSHOT/reactivestreams-1.0-20170509.055326-1-sources.jar.sha1
new file mode 100644
index 0000000..63a57f2
--- /dev/null
+++ b/samples-flatfoot/prebuilts0905/android/arch/lifecycle/reactivestreams/1.0-SNAPSHOT/reactivestreams-1.0-20170509.055326-1-sources.jar.sha1
@@ -0,0 +1 @@
+4be78d28a595a3780e81688377685fa3a8e6e762
\ No newline at end of file
diff --git a/samples-flatfoot/prebuilts0905/android/arch/lifecycle/reactivestreams/1.0-SNAPSHOT/reactivestreams-1.0-20170509.055326-1.aar b/samples-flatfoot/prebuilts0905/android/arch/lifecycle/reactivestreams/1.0-SNAPSHOT/reactivestreams-1.0-20170509.055326-1.aar
new file mode 100644
index 0000000..d3fbc8d
--- /dev/null
+++ b/samples-flatfoot/prebuilts0905/android/arch/lifecycle/reactivestreams/1.0-SNAPSHOT/reactivestreams-1.0-20170509.055326-1.aar
Binary files differ
diff --git a/samples-flatfoot/prebuilts0905/android/arch/lifecycle/reactivestreams/1.0-SNAPSHOT/reactivestreams-1.0-20170509.055326-1.aar.md5 b/samples-flatfoot/prebuilts0905/android/arch/lifecycle/reactivestreams/1.0-SNAPSHOT/reactivestreams-1.0-20170509.055326-1.aar.md5
new file mode 100644
index 0000000..ebf9646
--- /dev/null
+++ b/samples-flatfoot/prebuilts0905/android/arch/lifecycle/reactivestreams/1.0-SNAPSHOT/reactivestreams-1.0-20170509.055326-1.aar.md5
@@ -0,0 +1 @@
+7c9c2c20f41ca66ab06d29ba702717e1
\ No newline at end of file
diff --git a/samples-flatfoot/prebuilts0905/android/arch/lifecycle/reactivestreams/1.0-SNAPSHOT/reactivestreams-1.0-20170509.055326-1.aar.sha1 b/samples-flatfoot/prebuilts0905/android/arch/lifecycle/reactivestreams/1.0-SNAPSHOT/reactivestreams-1.0-20170509.055326-1.aar.sha1
new file mode 100644
index 0000000..3b283bd
--- /dev/null
+++ b/samples-flatfoot/prebuilts0905/android/arch/lifecycle/reactivestreams/1.0-SNAPSHOT/reactivestreams-1.0-20170509.055326-1.aar.sha1
@@ -0,0 +1 @@
+0ea07afc69041e728122223c405754daf3c6245b
\ No newline at end of file
diff --git a/samples-flatfoot/prebuilts0905/android/arch/lifecycle/reactivestreams/1.0-SNAPSHOT/reactivestreams-1.0-20170509.055326-1.pom b/samples-flatfoot/prebuilts0905/android/arch/lifecycle/reactivestreams/1.0-SNAPSHOT/reactivestreams-1.0-20170509.055326-1.pom
new file mode 100644
index 0000000..1acf4de
--- /dev/null
+++ b/samples-flatfoot/prebuilts0905/android/arch/lifecycle/reactivestreams/1.0-SNAPSHOT/reactivestreams-1.0-20170509.055326-1.pom
@@ -0,0 +1,47 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd" xmlns="http://maven.apache.org/POM/4.0.0"
+    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
+  <modelVersion>4.0.0</modelVersion>
+  <groupId>android.arch.lifecycle</groupId>
+  <artifactId>reactivestreams</artifactId>
+  <version>1.0-SNAPSHOT</version>
+  <packaging>aar</packaging>
+  <dependencies>
+    <dependency>
+      <groupId>android.arch.core</groupId>
+      <artifactId>core</artifactId>
+      <version>1.0-SNAPSHOT</version>
+      <scope>compile</scope>
+    </dependency>
+    <dependency>
+      <groupId>android.arch.lifecycle</groupId>
+      <artifactId>common</artifactId>
+      <version>1.0-SNAPSHOT</version>
+      <scope>compile</scope>
+    </dependency>
+    <dependency>
+      <groupId>android.arch.lifecycle</groupId>
+      <artifactId>extensions</artifactId>
+      <version>1.0-SNAPSHOT</version>
+      <scope>compile</scope>
+    </dependency>
+    <dependency>
+      <groupId>android.arch.lifecycle</groupId>
+      <artifactId>runtime</artifactId>
+      <version>1.0-SNAPSHOT</version>
+      <scope>compile</scope>
+    </dependency>
+    <dependency>
+      <groupId>com.android.support</groupId>
+      <artifactId>support-annotations</artifactId>
+      <version>25.2.0</version>
+      <scope>compile</scope>
+    </dependency>
+    <dependency>
+      <groupId>org.reactivestreams</groupId>
+      <artifactId>reactive-streams</artifactId>
+      <version>1.0.0</version>
+      <scope>compile</scope>
+    </dependency>
+  </dependencies>
+</project>
diff --git a/samples-flatfoot/prebuilts0905/android/arch/lifecycle/reactivestreams/1.0-SNAPSHOT/reactivestreams-1.0-20170509.055326-1.pom.md5 b/samples-flatfoot/prebuilts0905/android/arch/lifecycle/reactivestreams/1.0-SNAPSHOT/reactivestreams-1.0-20170509.055326-1.pom.md5
new file mode 100644
index 0000000..281c0ec
--- /dev/null
+++ b/samples-flatfoot/prebuilts0905/android/arch/lifecycle/reactivestreams/1.0-SNAPSHOT/reactivestreams-1.0-20170509.055326-1.pom.md5
@@ -0,0 +1 @@
+d100fe77c4b95264134281078880bf7c
\ No newline at end of file
diff --git a/samples-flatfoot/prebuilts0905/android/arch/lifecycle/reactivestreams/1.0-SNAPSHOT/reactivestreams-1.0-20170509.055326-1.pom.sha1 b/samples-flatfoot/prebuilts0905/android/arch/lifecycle/reactivestreams/1.0-SNAPSHOT/reactivestreams-1.0-20170509.055326-1.pom.sha1
new file mode 100644
index 0000000..83d8797
--- /dev/null
+++ b/samples-flatfoot/prebuilts0905/android/arch/lifecycle/reactivestreams/1.0-SNAPSHOT/reactivestreams-1.0-20170509.055326-1.pom.sha1
@@ -0,0 +1 @@
+0b1f6442bb8375850efc983bef2352cb17fbfb49
\ No newline at end of file
diff --git a/samples-flatfoot/prebuilts0905/android/arch/lifecycle/reactivestreams/maven-metadata.xml b/samples-flatfoot/prebuilts0905/android/arch/lifecycle/reactivestreams/maven-metadata.xml
new file mode 100644
index 0000000..219ace3
--- /dev/null
+++ b/samples-flatfoot/prebuilts0905/android/arch/lifecycle/reactivestreams/maven-metadata.xml
@@ -0,0 +1,11 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<metadata>
+  <groupId>android.arch.lifecycle</groupId>
+  <artifactId>reactivestreams</artifactId>
+  <versioning>
+    <versions>
+      <version>1.0-SNAPSHOT</version>
+    </versions>
+    <lastUpdated>20170509055326</lastUpdated>
+  </versioning>
+</metadata>
diff --git a/samples-flatfoot/prebuilts0905/android/arch/lifecycle/reactivestreams/maven-metadata.xml.md5 b/samples-flatfoot/prebuilts0905/android/arch/lifecycle/reactivestreams/maven-metadata.xml.md5
new file mode 100644
index 0000000..613ffc5
--- /dev/null
+++ b/samples-flatfoot/prebuilts0905/android/arch/lifecycle/reactivestreams/maven-metadata.xml.md5
@@ -0,0 +1 @@
+a8578e327c72aef75966f5d8e2dab594
\ No newline at end of file
diff --git a/samples-flatfoot/prebuilts0905/android/arch/lifecycle/reactivestreams/maven-metadata.xml.sha1 b/samples-flatfoot/prebuilts0905/android/arch/lifecycle/reactivestreams/maven-metadata.xml.sha1
new file mode 100644
index 0000000..a02462f
--- /dev/null
+++ b/samples-flatfoot/prebuilts0905/android/arch/lifecycle/reactivestreams/maven-metadata.xml.sha1
@@ -0,0 +1 @@
+bc5e3cca5166659a77527b953600fe9d9cc14f99
\ No newline at end of file
diff --git a/samples-flatfoot/prebuilts0905/android/arch/lifecycle/runtime/1.0-SNAPSHOT/maven-metadata.xml b/samples-flatfoot/prebuilts0905/android/arch/lifecycle/runtime/1.0-SNAPSHOT/maven-metadata.xml
new file mode 100644
index 0000000..07fcce9
--- /dev/null
+++ b/samples-flatfoot/prebuilts0905/android/arch/lifecycle/runtime/1.0-SNAPSHOT/maven-metadata.xml
@@ -0,0 +1,13 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<metadata>
+  <groupId>android.arch.lifecycle</groupId>
+  <artifactId>runtime</artifactId>
+  <version>1.0-SNAPSHOT</version>
+  <versioning>
+    <snapshot>
+      <timestamp>20170509.055323</timestamp>
+      <buildNumber>1</buildNumber>
+    </snapshot>
+    <lastUpdated>20170509055323</lastUpdated>
+  </versioning>
+</metadata>
diff --git a/samples-flatfoot/prebuilts0905/android/arch/lifecycle/runtime/1.0-SNAPSHOT/maven-metadata.xml.md5 b/samples-flatfoot/prebuilts0905/android/arch/lifecycle/runtime/1.0-SNAPSHOT/maven-metadata.xml.md5
new file mode 100644
index 0000000..0a6997e
--- /dev/null
+++ b/samples-flatfoot/prebuilts0905/android/arch/lifecycle/runtime/1.0-SNAPSHOT/maven-metadata.xml.md5
@@ -0,0 +1 @@
+119521b96ea5b84141125701eb521df0
\ No newline at end of file
diff --git a/samples-flatfoot/prebuilts0905/android/arch/lifecycle/runtime/1.0-SNAPSHOT/maven-metadata.xml.sha1 b/samples-flatfoot/prebuilts0905/android/arch/lifecycle/runtime/1.0-SNAPSHOT/maven-metadata.xml.sha1
new file mode 100644
index 0000000..021dc3e
--- /dev/null
+++ b/samples-flatfoot/prebuilts0905/android/arch/lifecycle/runtime/1.0-SNAPSHOT/maven-metadata.xml.sha1
@@ -0,0 +1 @@
+bb6a47101b4b0b10f21d31869325168d2282510b
\ No newline at end of file
diff --git a/samples-flatfoot/prebuilts0905/android/arch/lifecycle/runtime/1.0-SNAPSHOT/runtime-1.0-20170509.055323-1-sources.jar b/samples-flatfoot/prebuilts0905/android/arch/lifecycle/runtime/1.0-SNAPSHOT/runtime-1.0-20170509.055323-1-sources.jar
new file mode 100644
index 0000000..5c5b019
--- /dev/null
+++ b/samples-flatfoot/prebuilts0905/android/arch/lifecycle/runtime/1.0-SNAPSHOT/runtime-1.0-20170509.055323-1-sources.jar
Binary files differ
diff --git a/samples-flatfoot/prebuilts0905/android/arch/lifecycle/runtime/1.0-SNAPSHOT/runtime-1.0-20170509.055323-1-sources.jar.md5 b/samples-flatfoot/prebuilts0905/android/arch/lifecycle/runtime/1.0-SNAPSHOT/runtime-1.0-20170509.055323-1-sources.jar.md5
new file mode 100644
index 0000000..429dca5
--- /dev/null
+++ b/samples-flatfoot/prebuilts0905/android/arch/lifecycle/runtime/1.0-SNAPSHOT/runtime-1.0-20170509.055323-1-sources.jar.md5
@@ -0,0 +1 @@
+ddac4b3463d2bce5169b57550caf4897
\ No newline at end of file
diff --git a/samples-flatfoot/prebuilts0905/android/arch/lifecycle/runtime/1.0-SNAPSHOT/runtime-1.0-20170509.055323-1-sources.jar.sha1 b/samples-flatfoot/prebuilts0905/android/arch/lifecycle/runtime/1.0-SNAPSHOT/runtime-1.0-20170509.055323-1-sources.jar.sha1
new file mode 100644
index 0000000..a451feb
--- /dev/null
+++ b/samples-flatfoot/prebuilts0905/android/arch/lifecycle/runtime/1.0-SNAPSHOT/runtime-1.0-20170509.055323-1-sources.jar.sha1
@@ -0,0 +1 @@
+684636a06ea02c5c7c0edb05cef8b54b6904cd9a
\ No newline at end of file
diff --git a/samples-flatfoot/prebuilts0905/android/arch/lifecycle/runtime/1.0-SNAPSHOT/runtime-1.0-20170509.055323-1.aar b/samples-flatfoot/prebuilts0905/android/arch/lifecycle/runtime/1.0-SNAPSHOT/runtime-1.0-20170509.055323-1.aar
new file mode 100644
index 0000000..3977677
--- /dev/null
+++ b/samples-flatfoot/prebuilts0905/android/arch/lifecycle/runtime/1.0-SNAPSHOT/runtime-1.0-20170509.055323-1.aar
Binary files differ
diff --git a/samples-flatfoot/prebuilts0905/android/arch/lifecycle/runtime/1.0-SNAPSHOT/runtime-1.0-20170509.055323-1.aar.md5 b/samples-flatfoot/prebuilts0905/android/arch/lifecycle/runtime/1.0-SNAPSHOT/runtime-1.0-20170509.055323-1.aar.md5
new file mode 100644
index 0000000..e92c468
--- /dev/null
+++ b/samples-flatfoot/prebuilts0905/android/arch/lifecycle/runtime/1.0-SNAPSHOT/runtime-1.0-20170509.055323-1.aar.md5
@@ -0,0 +1 @@
+9378a6ef4cfdfa8edb8782ccea95cdf7
\ No newline at end of file
diff --git a/samples-flatfoot/prebuilts0905/android/arch/lifecycle/runtime/1.0-SNAPSHOT/runtime-1.0-20170509.055323-1.aar.sha1 b/samples-flatfoot/prebuilts0905/android/arch/lifecycle/runtime/1.0-SNAPSHOT/runtime-1.0-20170509.055323-1.aar.sha1
new file mode 100644
index 0000000..6e17fc4
--- /dev/null
+++ b/samples-flatfoot/prebuilts0905/android/arch/lifecycle/runtime/1.0-SNAPSHOT/runtime-1.0-20170509.055323-1.aar.sha1
@@ -0,0 +1 @@
+df3ef640c9deb3ff016704de2730b2f8b45f9fd4
\ No newline at end of file
diff --git a/samples-flatfoot/prebuilts0905/android/arch/lifecycle/runtime/1.0-SNAPSHOT/runtime-1.0-20170509.055323-1.pom b/samples-flatfoot/prebuilts0905/android/arch/lifecycle/runtime/1.0-SNAPSHOT/runtime-1.0-20170509.055323-1.pom
new file mode 100644
index 0000000..7277c40
--- /dev/null
+++ b/samples-flatfoot/prebuilts0905/android/arch/lifecycle/runtime/1.0-SNAPSHOT/runtime-1.0-20170509.055323-1.pom
@@ -0,0 +1,41 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd" xmlns="http://maven.apache.org/POM/4.0.0"
+    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
+  <modelVersion>4.0.0</modelVersion>
+  <groupId>android.arch.lifecycle</groupId>
+  <artifactId>runtime</artifactId>
+  <version>1.0-SNAPSHOT</version>
+  <packaging>aar</packaging>
+  <dependencies>
+    <dependency>
+      <groupId>android.arch.lifecycle</groupId>
+      <artifactId>common</artifactId>
+      <version>1.0-SNAPSHOT</version>
+      <scope>compile</scope>
+    </dependency>
+    <dependency>
+      <groupId>android.arch.core</groupId>
+      <artifactId>core</artifactId>
+      <version>1.0-SNAPSHOT</version>
+      <scope>compile</scope>
+    </dependency>
+    <dependency>
+      <groupId>com.android.support</groupId>
+      <artifactId>support-core-utils</artifactId>
+      <version>25.2.0</version>
+      <scope>compile</scope>
+    </dependency>
+    <dependency>
+      <groupId>com.android.support</groupId>
+      <artifactId>support-fragment</artifactId>
+      <version>25.2.0</version>
+      <scope>compile</scope>
+    </dependency>
+    <dependency>
+      <groupId>com.android.support</groupId>
+      <artifactId>support-annotations</artifactId>
+      <version>25.2.0</version>
+      <scope>compile</scope>
+    </dependency>
+  </dependencies>
+</project>
diff --git a/samples-flatfoot/prebuilts0905/android/arch/lifecycle/runtime/1.0-SNAPSHOT/runtime-1.0-20170509.055323-1.pom.md5 b/samples-flatfoot/prebuilts0905/android/arch/lifecycle/runtime/1.0-SNAPSHOT/runtime-1.0-20170509.055323-1.pom.md5
new file mode 100644
index 0000000..8a3f144
--- /dev/null
+++ b/samples-flatfoot/prebuilts0905/android/arch/lifecycle/runtime/1.0-SNAPSHOT/runtime-1.0-20170509.055323-1.pom.md5
@@ -0,0 +1 @@
+cdb420b579966da6376ed24a5ae83b69
\ No newline at end of file
diff --git a/samples-flatfoot/prebuilts0905/android/arch/lifecycle/runtime/1.0-SNAPSHOT/runtime-1.0-20170509.055323-1.pom.sha1 b/samples-flatfoot/prebuilts0905/android/arch/lifecycle/runtime/1.0-SNAPSHOT/runtime-1.0-20170509.055323-1.pom.sha1
new file mode 100644
index 0000000..5cc15e7
--- /dev/null
+++ b/samples-flatfoot/prebuilts0905/android/arch/lifecycle/runtime/1.0-SNAPSHOT/runtime-1.0-20170509.055323-1.pom.sha1
@@ -0,0 +1 @@
+58d1017ee4b7026ce71d4f5d8be32615332f3875
\ No newline at end of file
diff --git a/samples-flatfoot/prebuilts0905/android/arch/lifecycle/runtime/maven-metadata.xml b/samples-flatfoot/prebuilts0905/android/arch/lifecycle/runtime/maven-metadata.xml
new file mode 100644
index 0000000..f61cff8
--- /dev/null
+++ b/samples-flatfoot/prebuilts0905/android/arch/lifecycle/runtime/maven-metadata.xml
@@ -0,0 +1,11 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<metadata>
+  <groupId>android.arch.lifecycle</groupId>
+  <artifactId>runtime</artifactId>
+  <versioning>
+    <versions>
+      <version>1.0-SNAPSHOT</version>
+    </versions>
+    <lastUpdated>20170509055323</lastUpdated>
+  </versioning>
+</metadata>
diff --git a/samples-flatfoot/prebuilts0905/android/arch/lifecycle/runtime/maven-metadata.xml.md5 b/samples-flatfoot/prebuilts0905/android/arch/lifecycle/runtime/maven-metadata.xml.md5
new file mode 100644
index 0000000..cf3ad70
--- /dev/null
+++ b/samples-flatfoot/prebuilts0905/android/arch/lifecycle/runtime/maven-metadata.xml.md5
@@ -0,0 +1 @@
+d8899f99ced40f4ad3bbd8fbe36841f1
\ No newline at end of file
diff --git a/samples-flatfoot/prebuilts0905/android/arch/lifecycle/runtime/maven-metadata.xml.sha1 b/samples-flatfoot/prebuilts0905/android/arch/lifecycle/runtime/maven-metadata.xml.sha1
new file mode 100644
index 0000000..d73bdaf
--- /dev/null
+++ b/samples-flatfoot/prebuilts0905/android/arch/lifecycle/runtime/maven-metadata.xml.sha1
@@ -0,0 +1 @@
+a8a7e9043d27353cfe1365687757e8585a004695
\ No newline at end of file
diff --git a/samples-flatfoot/prebuilts0905/android/arch/persistence/room/common/1.0-SNAPSHOT/common-1.0-20170509.055319-1-sources.jar b/samples-flatfoot/prebuilts0905/android/arch/persistence/room/common/1.0-SNAPSHOT/common-1.0-20170509.055319-1-sources.jar
new file mode 100644
index 0000000..edd10aa
--- /dev/null
+++ b/samples-flatfoot/prebuilts0905/android/arch/persistence/room/common/1.0-SNAPSHOT/common-1.0-20170509.055319-1-sources.jar
Binary files differ
diff --git a/samples-flatfoot/prebuilts0905/android/arch/persistence/room/common/1.0-SNAPSHOT/common-1.0-20170509.055319-1-sources.jar.md5 b/samples-flatfoot/prebuilts0905/android/arch/persistence/room/common/1.0-SNAPSHOT/common-1.0-20170509.055319-1-sources.jar.md5
new file mode 100644
index 0000000..3924784
--- /dev/null
+++ b/samples-flatfoot/prebuilts0905/android/arch/persistence/room/common/1.0-SNAPSHOT/common-1.0-20170509.055319-1-sources.jar.md5
@@ -0,0 +1 @@
+d96a6026f4f80ce4d6b89c0fc4303796
\ No newline at end of file
diff --git a/samples-flatfoot/prebuilts0905/android/arch/persistence/room/common/1.0-SNAPSHOT/common-1.0-20170509.055319-1-sources.jar.sha1 b/samples-flatfoot/prebuilts0905/android/arch/persistence/room/common/1.0-SNAPSHOT/common-1.0-20170509.055319-1-sources.jar.sha1
new file mode 100644
index 0000000..ed8b25e
--- /dev/null
+++ b/samples-flatfoot/prebuilts0905/android/arch/persistence/room/common/1.0-SNAPSHOT/common-1.0-20170509.055319-1-sources.jar.sha1
@@ -0,0 +1 @@
+5d525be81aafd7f9dcdf97256ebc048c9eabcb07
\ No newline at end of file
diff --git a/samples-flatfoot/prebuilts0905/android/arch/persistence/room/common/1.0-SNAPSHOT/common-1.0-20170509.055319-1.jar b/samples-flatfoot/prebuilts0905/android/arch/persistence/room/common/1.0-SNAPSHOT/common-1.0-20170509.055319-1.jar
new file mode 100644
index 0000000..2d13c79
--- /dev/null
+++ b/samples-flatfoot/prebuilts0905/android/arch/persistence/room/common/1.0-SNAPSHOT/common-1.0-20170509.055319-1.jar
Binary files differ
diff --git a/samples-flatfoot/prebuilts0905/android/arch/persistence/room/common/1.0-SNAPSHOT/common-1.0-20170509.055319-1.jar.md5 b/samples-flatfoot/prebuilts0905/android/arch/persistence/room/common/1.0-SNAPSHOT/common-1.0-20170509.055319-1.jar.md5
new file mode 100644
index 0000000..653ce9e
--- /dev/null
+++ b/samples-flatfoot/prebuilts0905/android/arch/persistence/room/common/1.0-SNAPSHOT/common-1.0-20170509.055319-1.jar.md5
@@ -0,0 +1 @@
+617e423f1eb5587f924f26c92cf6fe7c
\ No newline at end of file
diff --git a/samples-flatfoot/prebuilts0905/android/arch/persistence/room/common/1.0-SNAPSHOT/common-1.0-20170509.055319-1.jar.sha1 b/samples-flatfoot/prebuilts0905/android/arch/persistence/room/common/1.0-SNAPSHOT/common-1.0-20170509.055319-1.jar.sha1
new file mode 100644
index 0000000..0994ece
--- /dev/null
+++ b/samples-flatfoot/prebuilts0905/android/arch/persistence/room/common/1.0-SNAPSHOT/common-1.0-20170509.055319-1.jar.sha1
@@ -0,0 +1 @@
+8f2696a4d237db1a136040470d52e7d8509c5e76
\ No newline at end of file
diff --git a/samples-flatfoot/prebuilts0905/android/arch/persistence/room/common/1.0-SNAPSHOT/common-1.0-20170509.055319-1.pom b/samples-flatfoot/prebuilts0905/android/arch/persistence/room/common/1.0-SNAPSHOT/common-1.0-20170509.055319-1.pom
new file mode 100644
index 0000000..9e1e0a7
--- /dev/null
+++ b/samples-flatfoot/prebuilts0905/android/arch/persistence/room/common/1.0-SNAPSHOT/common-1.0-20170509.055319-1.pom
@@ -0,0 +1,28 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd" xmlns="http://maven.apache.org/POM/4.0.0"
+    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
+  <modelVersion>4.0.0</modelVersion>
+  <groupId>android.arch.persistence.room</groupId>
+  <artifactId>common</artifactId>
+  <version>1.0-SNAPSHOT</version>
+  <dependencies>
+    <dependency>
+      <groupId>com.android.support</groupId>
+      <artifactId>support-annotations</artifactId>
+      <version>25.2.0</version>
+      <scope>compile</scope>
+    </dependency>
+    <dependency>
+      <groupId>junit</groupId>
+      <artifactId>junit</artifactId>
+      <version>4.12</version>
+      <scope>test</scope>
+    </dependency>
+    <dependency>
+      <groupId>org.mockito</groupId>
+      <artifactId>mockito-core</artifactId>
+      <version>1.9.5</version>
+      <scope>test</scope>
+    </dependency>
+  </dependencies>
+</project>
diff --git a/samples-flatfoot/prebuilts0905/android/arch/persistence/room/common/1.0-SNAPSHOT/common-1.0-20170509.055319-1.pom.md5 b/samples-flatfoot/prebuilts0905/android/arch/persistence/room/common/1.0-SNAPSHOT/common-1.0-20170509.055319-1.pom.md5
new file mode 100644
index 0000000..337b77b
--- /dev/null
+++ b/samples-flatfoot/prebuilts0905/android/arch/persistence/room/common/1.0-SNAPSHOT/common-1.0-20170509.055319-1.pom.md5
@@ -0,0 +1 @@
+d2d34afe4df97e3cc011ea5767004ab1
\ No newline at end of file
diff --git a/samples-flatfoot/prebuilts0905/android/arch/persistence/room/common/1.0-SNAPSHOT/common-1.0-20170509.055319-1.pom.sha1 b/samples-flatfoot/prebuilts0905/android/arch/persistence/room/common/1.0-SNAPSHOT/common-1.0-20170509.055319-1.pom.sha1
new file mode 100644
index 0000000..7aed843
--- /dev/null
+++ b/samples-flatfoot/prebuilts0905/android/arch/persistence/room/common/1.0-SNAPSHOT/common-1.0-20170509.055319-1.pom.sha1
@@ -0,0 +1 @@
+cfb361baaef3ea8286b0b20785d6e1fac68abf38
\ No newline at end of file
diff --git a/samples-flatfoot/prebuilts0905/android/arch/persistence/room/common/1.0-SNAPSHOT/maven-metadata.xml b/samples-flatfoot/prebuilts0905/android/arch/persistence/room/common/1.0-SNAPSHOT/maven-metadata.xml
new file mode 100644
index 0000000..4f8bf1d
--- /dev/null
+++ b/samples-flatfoot/prebuilts0905/android/arch/persistence/room/common/1.0-SNAPSHOT/maven-metadata.xml
@@ -0,0 +1,13 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<metadata>
+  <groupId>android.arch.persistence.room</groupId>
+  <artifactId>common</artifactId>
+  <version>1.0-SNAPSHOT</version>
+  <versioning>
+    <snapshot>
+      <timestamp>20170509.055319</timestamp>
+      <buildNumber>1</buildNumber>
+    </snapshot>
+    <lastUpdated>20170509055319</lastUpdated>
+  </versioning>
+</metadata>
diff --git a/samples-flatfoot/prebuilts0905/android/arch/persistence/room/common/1.0-SNAPSHOT/maven-metadata.xml.md5 b/samples-flatfoot/prebuilts0905/android/arch/persistence/room/common/1.0-SNAPSHOT/maven-metadata.xml.md5
new file mode 100644
index 0000000..62b1eb2
--- /dev/null
+++ b/samples-flatfoot/prebuilts0905/android/arch/persistence/room/common/1.0-SNAPSHOT/maven-metadata.xml.md5
@@ -0,0 +1 @@
+f87d8e9973f56fdafd9815f8642f3358
\ No newline at end of file
diff --git a/samples-flatfoot/prebuilts0905/android/arch/persistence/room/common/1.0-SNAPSHOT/maven-metadata.xml.sha1 b/samples-flatfoot/prebuilts0905/android/arch/persistence/room/common/1.0-SNAPSHOT/maven-metadata.xml.sha1
new file mode 100644
index 0000000..330cc7f
--- /dev/null
+++ b/samples-flatfoot/prebuilts0905/android/arch/persistence/room/common/1.0-SNAPSHOT/maven-metadata.xml.sha1
@@ -0,0 +1 @@
+2110a03050a782cb1b95ec525e6039429b40d74f
\ No newline at end of file
diff --git a/samples-flatfoot/prebuilts0905/android/arch/persistence/room/common/maven-metadata.xml b/samples-flatfoot/prebuilts0905/android/arch/persistence/room/common/maven-metadata.xml
new file mode 100644
index 0000000..4cb4c36
--- /dev/null
+++ b/samples-flatfoot/prebuilts0905/android/arch/persistence/room/common/maven-metadata.xml
@@ -0,0 +1,11 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<metadata>
+  <groupId>android.arch.persistence.room</groupId>
+  <artifactId>common</artifactId>
+  <versioning>
+    <versions>
+      <version>1.0-SNAPSHOT</version>
+    </versions>
+    <lastUpdated>20170509055319</lastUpdated>
+  </versioning>
+</metadata>
diff --git a/samples-flatfoot/prebuilts0905/android/arch/persistence/room/common/maven-metadata.xml.md5 b/samples-flatfoot/prebuilts0905/android/arch/persistence/room/common/maven-metadata.xml.md5
new file mode 100644
index 0000000..600e038
--- /dev/null
+++ b/samples-flatfoot/prebuilts0905/android/arch/persistence/room/common/maven-metadata.xml.md5
@@ -0,0 +1 @@
+7fc679dffa8896ced94b1ba240424f1a
\ No newline at end of file
diff --git a/samples-flatfoot/prebuilts0905/android/arch/persistence/room/common/maven-metadata.xml.sha1 b/samples-flatfoot/prebuilts0905/android/arch/persistence/room/common/maven-metadata.xml.sha1
new file mode 100644
index 0000000..d0db785
--- /dev/null
+++ b/samples-flatfoot/prebuilts0905/android/arch/persistence/room/common/maven-metadata.xml.sha1
@@ -0,0 +1 @@
+9f66a6fcbce5453296232fbab25c009bf6ebc2d3
\ No newline at end of file
diff --git a/samples-flatfoot/prebuilts0905/android/arch/persistence/room/compiler/1.0-SNAPSHOT/compiler-1.0-20170509.055353-1.jar b/samples-flatfoot/prebuilts0905/android/arch/persistence/room/compiler/1.0-SNAPSHOT/compiler-1.0-20170509.055353-1.jar
new file mode 100644
index 0000000..87008fd
--- /dev/null
+++ b/samples-flatfoot/prebuilts0905/android/arch/persistence/room/compiler/1.0-SNAPSHOT/compiler-1.0-20170509.055353-1.jar
Binary files differ
diff --git a/samples-flatfoot/prebuilts0905/android/arch/persistence/room/compiler/1.0-SNAPSHOT/compiler-1.0-20170509.055353-1.jar.md5 b/samples-flatfoot/prebuilts0905/android/arch/persistence/room/compiler/1.0-SNAPSHOT/compiler-1.0-20170509.055353-1.jar.md5
new file mode 100644
index 0000000..5dab906
--- /dev/null
+++ b/samples-flatfoot/prebuilts0905/android/arch/persistence/room/compiler/1.0-SNAPSHOT/compiler-1.0-20170509.055353-1.jar.md5
@@ -0,0 +1 @@
+878684478cf5fcd81aa5961c0bf8c940
\ No newline at end of file
diff --git a/samples-flatfoot/prebuilts0905/android/arch/persistence/room/compiler/1.0-SNAPSHOT/compiler-1.0-20170509.055353-1.jar.sha1 b/samples-flatfoot/prebuilts0905/android/arch/persistence/room/compiler/1.0-SNAPSHOT/compiler-1.0-20170509.055353-1.jar.sha1
new file mode 100644
index 0000000..cda2f71
--- /dev/null
+++ b/samples-flatfoot/prebuilts0905/android/arch/persistence/room/compiler/1.0-SNAPSHOT/compiler-1.0-20170509.055353-1.jar.sha1
@@ -0,0 +1 @@
+01fbebc823191731e95eb090598bddb71c770846
\ No newline at end of file
diff --git a/samples-flatfoot/prebuilts0905/android/arch/persistence/room/compiler/1.0-SNAPSHOT/compiler-1.0-20170509.055353-1.pom b/samples-flatfoot/prebuilts0905/android/arch/persistence/room/compiler/1.0-SNAPSHOT/compiler-1.0-20170509.055353-1.pom
new file mode 100644
index 0000000..efe6593
--- /dev/null
+++ b/samples-flatfoot/prebuilts0905/android/arch/persistence/room/compiler/1.0-SNAPSHOT/compiler-1.0-20170509.055353-1.pom
@@ -0,0 +1,82 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd" xmlns="http://maven.apache.org/POM/4.0.0"
+    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
+  <modelVersion>4.0.0</modelVersion>
+  <groupId>android.arch.persistence.room</groupId>
+  <artifactId>compiler</artifactId>
+  <version>1.0-SNAPSHOT</version>
+  <dependencies>
+    <dependency>
+      <groupId>android.arch.persistence.room</groupId>
+      <artifactId>common</artifactId>
+      <version>1.0-SNAPSHOT</version>
+      <scope>compile</scope>
+    </dependency>
+    <dependency>
+      <groupId>android.arch.persistence.room</groupId>
+      <artifactId>migration</artifactId>
+      <version>1.0-SNAPSHOT</version>
+      <scope>compile</scope>
+    </dependency>
+    <dependency>
+      <groupId>org.jetbrains.kotlin</groupId>
+      <artifactId>kotlin-stdlib</artifactId>
+      <version>1.1.1</version>
+      <scope>compile</scope>
+    </dependency>
+    <dependency>
+      <groupId>com.google.auto</groupId>
+      <artifactId>auto-common</artifactId>
+      <version>0.6</version>
+      <scope>compile</scope>
+    </dependency>
+    <dependency>
+      <groupId>com.squareup</groupId>
+      <artifactId>javapoet</artifactId>
+      <version>1.8.0</version>
+      <scope>compile</scope>
+    </dependency>
+    <dependency>
+      <groupId>org.antlr</groupId>
+      <artifactId>antlr4</artifactId>
+      <version>4.5.3</version>
+      <scope>compile</scope>
+    </dependency>
+    <dependency>
+      <groupId>org.xerial</groupId>
+      <artifactId>sqlite-jdbc</artifactId>
+      <version>3.16.1</version>
+      <scope>compile</scope>
+    </dependency>
+    <dependency>
+      <groupId>commons-codec</groupId>
+      <artifactId>commons-codec</artifactId>
+      <version>1.10</version>
+      <scope>compile</scope>
+    </dependency>
+    <dependency>
+      <groupId>com.google.testing.compile</groupId>
+      <artifactId>compile-testing</artifactId>
+      <version>0.9</version>
+      <scope>test</scope>
+    </dependency>
+    <dependency>
+      <groupId>junit</groupId>
+      <artifactId>junit</artifactId>
+      <version>4.12</version>
+      <scope>test</scope>
+    </dependency>
+    <dependency>
+      <groupId>com.intellij</groupId>
+      <artifactId>annotations</artifactId>
+      <version>12.0</version>
+      <scope>test</scope>
+    </dependency>
+    <dependency>
+      <groupId>org.mockito</groupId>
+      <artifactId>mockito-core</artifactId>
+      <version>1.9.5</version>
+      <scope>test</scope>
+    </dependency>
+  </dependencies>
+</project>
diff --git a/samples-flatfoot/prebuilts0905/android/arch/persistence/room/compiler/1.0-SNAPSHOT/compiler-1.0-20170509.055353-1.pom.md5 b/samples-flatfoot/prebuilts0905/android/arch/persistence/room/compiler/1.0-SNAPSHOT/compiler-1.0-20170509.055353-1.pom.md5
new file mode 100644
index 0000000..488d62c
--- /dev/null
+++ b/samples-flatfoot/prebuilts0905/android/arch/persistence/room/compiler/1.0-SNAPSHOT/compiler-1.0-20170509.055353-1.pom.md5
@@ -0,0 +1 @@
+48fb3ae050b678360a30cb666cf9a1c6
\ No newline at end of file
diff --git a/samples-flatfoot/prebuilts0905/android/arch/persistence/room/compiler/1.0-SNAPSHOT/compiler-1.0-20170509.055353-1.pom.sha1 b/samples-flatfoot/prebuilts0905/android/arch/persistence/room/compiler/1.0-SNAPSHOT/compiler-1.0-20170509.055353-1.pom.sha1
new file mode 100644
index 0000000..75933e7
--- /dev/null
+++ b/samples-flatfoot/prebuilts0905/android/arch/persistence/room/compiler/1.0-SNAPSHOT/compiler-1.0-20170509.055353-1.pom.sha1
@@ -0,0 +1 @@
+e4542a5347e599d1d47698c0d22b9896b143aea1
\ No newline at end of file
diff --git a/samples-flatfoot/prebuilts0905/android/arch/persistence/room/compiler/1.0-SNAPSHOT/maven-metadata.xml b/samples-flatfoot/prebuilts0905/android/arch/persistence/room/compiler/1.0-SNAPSHOT/maven-metadata.xml
new file mode 100644
index 0000000..8ce1efb
--- /dev/null
+++ b/samples-flatfoot/prebuilts0905/android/arch/persistence/room/compiler/1.0-SNAPSHOT/maven-metadata.xml
@@ -0,0 +1,13 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<metadata>
+  <groupId>android.arch.persistence.room</groupId>
+  <artifactId>compiler</artifactId>
+  <version>1.0-SNAPSHOT</version>
+  <versioning>
+    <snapshot>
+      <timestamp>20170509.055353</timestamp>
+      <buildNumber>1</buildNumber>
+    </snapshot>
+    <lastUpdated>20170509055353</lastUpdated>
+  </versioning>
+</metadata>
diff --git a/samples-flatfoot/prebuilts0905/android/arch/persistence/room/compiler/1.0-SNAPSHOT/maven-metadata.xml.md5 b/samples-flatfoot/prebuilts0905/android/arch/persistence/room/compiler/1.0-SNAPSHOT/maven-metadata.xml.md5
new file mode 100644
index 0000000..7b11f7b
--- /dev/null
+++ b/samples-flatfoot/prebuilts0905/android/arch/persistence/room/compiler/1.0-SNAPSHOT/maven-metadata.xml.md5
@@ -0,0 +1 @@
+968ac62f039514702b626c638098a1c8
\ No newline at end of file
diff --git a/samples-flatfoot/prebuilts0905/android/arch/persistence/room/compiler/1.0-SNAPSHOT/maven-metadata.xml.sha1 b/samples-flatfoot/prebuilts0905/android/arch/persistence/room/compiler/1.0-SNAPSHOT/maven-metadata.xml.sha1
new file mode 100644
index 0000000..0f271a2
--- /dev/null
+++ b/samples-flatfoot/prebuilts0905/android/arch/persistence/room/compiler/1.0-SNAPSHOT/maven-metadata.xml.sha1
@@ -0,0 +1 @@
+077974f948717100560d289e57c7dfdf64ec9ffb
\ No newline at end of file
diff --git a/samples-flatfoot/prebuilts0905/android/arch/persistence/room/compiler/maven-metadata.xml b/samples-flatfoot/prebuilts0905/android/arch/persistence/room/compiler/maven-metadata.xml
new file mode 100644
index 0000000..9789469
--- /dev/null
+++ b/samples-flatfoot/prebuilts0905/android/arch/persistence/room/compiler/maven-metadata.xml
@@ -0,0 +1,11 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<metadata>
+  <groupId>android.arch.persistence.room</groupId>
+  <artifactId>compiler</artifactId>
+  <versioning>
+    <versions>
+      <version>1.0-SNAPSHOT</version>
+    </versions>
+    <lastUpdated>20170509055353</lastUpdated>
+  </versioning>
+</metadata>
diff --git a/samples-flatfoot/prebuilts0905/android/arch/persistence/room/compiler/maven-metadata.xml.md5 b/samples-flatfoot/prebuilts0905/android/arch/persistence/room/compiler/maven-metadata.xml.md5
new file mode 100644
index 0000000..19b39d5
--- /dev/null
+++ b/samples-flatfoot/prebuilts0905/android/arch/persistence/room/compiler/maven-metadata.xml.md5
@@ -0,0 +1 @@
+971636fd89a0d54a36cf44c563013f86
\ No newline at end of file
diff --git a/samples-flatfoot/prebuilts0905/android/arch/persistence/room/compiler/maven-metadata.xml.sha1 b/samples-flatfoot/prebuilts0905/android/arch/persistence/room/compiler/maven-metadata.xml.sha1
new file mode 100644
index 0000000..6ed6e2c
--- /dev/null
+++ b/samples-flatfoot/prebuilts0905/android/arch/persistence/room/compiler/maven-metadata.xml.sha1
@@ -0,0 +1 @@
+f4c75f7d9c9d2a8b435d8bf5dc3a76c9f8b208c0
\ No newline at end of file
diff --git a/samples-flatfoot/prebuilts0905/android/arch/persistence/room/migration/1.0-SNAPSHOT/maven-metadata.xml b/samples-flatfoot/prebuilts0905/android/arch/persistence/room/migration/1.0-SNAPSHOT/maven-metadata.xml
new file mode 100644
index 0000000..d2b3c09
--- /dev/null
+++ b/samples-flatfoot/prebuilts0905/android/arch/persistence/room/migration/1.0-SNAPSHOT/maven-metadata.xml
@@ -0,0 +1,13 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<metadata>
+  <groupId>android.arch.persistence.room</groupId>
+  <artifactId>migration</artifactId>
+  <version>1.0-SNAPSHOT</version>
+  <versioning>
+    <snapshot>
+      <timestamp>20170509.055319</timestamp>
+      <buildNumber>1</buildNumber>
+    </snapshot>
+    <lastUpdated>20170509055319</lastUpdated>
+  </versioning>
+</metadata>
diff --git a/samples-flatfoot/prebuilts0905/android/arch/persistence/room/migration/1.0-SNAPSHOT/maven-metadata.xml.md5 b/samples-flatfoot/prebuilts0905/android/arch/persistence/room/migration/1.0-SNAPSHOT/maven-metadata.xml.md5
new file mode 100644
index 0000000..48d33d0
--- /dev/null
+++ b/samples-flatfoot/prebuilts0905/android/arch/persistence/room/migration/1.0-SNAPSHOT/maven-metadata.xml.md5
@@ -0,0 +1 @@
+d506023a946730d4c5a41001626247f1
\ No newline at end of file
diff --git a/samples-flatfoot/prebuilts0905/android/arch/persistence/room/migration/1.0-SNAPSHOT/maven-metadata.xml.sha1 b/samples-flatfoot/prebuilts0905/android/arch/persistence/room/migration/1.0-SNAPSHOT/maven-metadata.xml.sha1
new file mode 100644
index 0000000..6660c9b
--- /dev/null
+++ b/samples-flatfoot/prebuilts0905/android/arch/persistence/room/migration/1.0-SNAPSHOT/maven-metadata.xml.sha1
@@ -0,0 +1 @@
+da8833c7dcb320a7f548e52943a3486019bc16ed
\ No newline at end of file
diff --git a/samples-flatfoot/prebuilts0905/android/arch/persistence/room/migration/1.0-SNAPSHOT/migration-1.0-20170509.055319-1.jar b/samples-flatfoot/prebuilts0905/android/arch/persistence/room/migration/1.0-SNAPSHOT/migration-1.0-20170509.055319-1.jar
new file mode 100644
index 0000000..fd9cd07
--- /dev/null
+++ b/samples-flatfoot/prebuilts0905/android/arch/persistence/room/migration/1.0-SNAPSHOT/migration-1.0-20170509.055319-1.jar
Binary files differ
diff --git a/samples-flatfoot/prebuilts0905/android/arch/persistence/room/migration/1.0-SNAPSHOT/migration-1.0-20170509.055319-1.jar.md5 b/samples-flatfoot/prebuilts0905/android/arch/persistence/room/migration/1.0-SNAPSHOT/migration-1.0-20170509.055319-1.jar.md5
new file mode 100644
index 0000000..925253c
--- /dev/null
+++ b/samples-flatfoot/prebuilts0905/android/arch/persistence/room/migration/1.0-SNAPSHOT/migration-1.0-20170509.055319-1.jar.md5
@@ -0,0 +1 @@
+60b4e759c801c0352ede3896ddae748a
\ No newline at end of file
diff --git a/samples-flatfoot/prebuilts0905/android/arch/persistence/room/migration/1.0-SNAPSHOT/migration-1.0-20170509.055319-1.jar.sha1 b/samples-flatfoot/prebuilts0905/android/arch/persistence/room/migration/1.0-SNAPSHOT/migration-1.0-20170509.055319-1.jar.sha1
new file mode 100644
index 0000000..44a46ad
--- /dev/null
+++ b/samples-flatfoot/prebuilts0905/android/arch/persistence/room/migration/1.0-SNAPSHOT/migration-1.0-20170509.055319-1.jar.sha1
@@ -0,0 +1 @@
+9cd324c2a179d054e2211967c22b806fe8af955e
\ No newline at end of file
diff --git a/samples-flatfoot/prebuilts0905/android/arch/persistence/room/migration/1.0-SNAPSHOT/migration-1.0-20170509.055319-1.pom b/samples-flatfoot/prebuilts0905/android/arch/persistence/room/migration/1.0-SNAPSHOT/migration-1.0-20170509.055319-1.pom
new file mode 100644
index 0000000..9d8a6fa
--- /dev/null
+++ b/samples-flatfoot/prebuilts0905/android/arch/persistence/room/migration/1.0-SNAPSHOT/migration-1.0-20170509.055319-1.pom
@@ -0,0 +1,46 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd" xmlns="http://maven.apache.org/POM/4.0.0"
+    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
+  <modelVersion>4.0.0</modelVersion>
+  <groupId>android.arch.persistence.room</groupId>
+  <artifactId>migration</artifactId>
+  <version>1.0-SNAPSHOT</version>
+  <dependencies>
+    <dependency>
+      <groupId>android.arch.persistence.room</groupId>
+      <artifactId>common</artifactId>
+      <version>1.0-SNAPSHOT</version>
+      <scope>compile</scope>
+    </dependency>
+    <dependency>
+      <groupId>org.jetbrains.kotlin</groupId>
+      <artifactId>kotlin-stdlib</artifactId>
+      <version>1.1.1</version>
+      <scope>compile</scope>
+    </dependency>
+    <dependency>
+      <groupId>com.google.code.gson</groupId>
+      <artifactId>gson</artifactId>
+      <version>2.8.0</version>
+      <scope>compile</scope>
+    </dependency>
+    <dependency>
+      <groupId>junit</groupId>
+      <artifactId>junit</artifactId>
+      <version>4.12</version>
+      <scope>test</scope>
+    </dependency>
+    <dependency>
+      <groupId>com.intellij</groupId>
+      <artifactId>annotations</artifactId>
+      <version>12.0</version>
+      <scope>test</scope>
+    </dependency>
+    <dependency>
+      <groupId>org.mockito</groupId>
+      <artifactId>mockito-core</artifactId>
+      <version>1.9.5</version>
+      <scope>test</scope>
+    </dependency>
+  </dependencies>
+</project>
diff --git a/samples-flatfoot/prebuilts0905/android/arch/persistence/room/migration/1.0-SNAPSHOT/migration-1.0-20170509.055319-1.pom.md5 b/samples-flatfoot/prebuilts0905/android/arch/persistence/room/migration/1.0-SNAPSHOT/migration-1.0-20170509.055319-1.pom.md5
new file mode 100644
index 0000000..33a93b0
--- /dev/null
+++ b/samples-flatfoot/prebuilts0905/android/arch/persistence/room/migration/1.0-SNAPSHOT/migration-1.0-20170509.055319-1.pom.md5
@@ -0,0 +1 @@
+0a669e52701c46ecadc30de6c051a639
\ No newline at end of file
diff --git a/samples-flatfoot/prebuilts0905/android/arch/persistence/room/migration/1.0-SNAPSHOT/migration-1.0-20170509.055319-1.pom.sha1 b/samples-flatfoot/prebuilts0905/android/arch/persistence/room/migration/1.0-SNAPSHOT/migration-1.0-20170509.055319-1.pom.sha1
new file mode 100644
index 0000000..0a70f31
--- /dev/null
+++ b/samples-flatfoot/prebuilts0905/android/arch/persistence/room/migration/1.0-SNAPSHOT/migration-1.0-20170509.055319-1.pom.sha1
@@ -0,0 +1 @@
+a63a70d662d9323509cb6a572a796758fe9db461
\ No newline at end of file
diff --git a/samples-flatfoot/prebuilts0905/android/arch/persistence/room/migration/maven-metadata.xml b/samples-flatfoot/prebuilts0905/android/arch/persistence/room/migration/maven-metadata.xml
new file mode 100644
index 0000000..ebb0a29
--- /dev/null
+++ b/samples-flatfoot/prebuilts0905/android/arch/persistence/room/migration/maven-metadata.xml
@@ -0,0 +1,11 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<metadata>
+  <groupId>android.arch.persistence.room</groupId>
+  <artifactId>migration</artifactId>
+  <versioning>
+    <versions>
+      <version>1.0-SNAPSHOT</version>
+    </versions>
+    <lastUpdated>20170509055319</lastUpdated>
+  </versioning>
+</metadata>
diff --git a/samples-flatfoot/prebuilts0905/android/arch/persistence/room/migration/maven-metadata.xml.md5 b/samples-flatfoot/prebuilts0905/android/arch/persistence/room/migration/maven-metadata.xml.md5
new file mode 100644
index 0000000..978b8a7
--- /dev/null
+++ b/samples-flatfoot/prebuilts0905/android/arch/persistence/room/migration/maven-metadata.xml.md5
@@ -0,0 +1 @@
+99b3b410dc02180c9c0c10c3781fa579
\ No newline at end of file
diff --git a/samples-flatfoot/prebuilts0905/android/arch/persistence/room/migration/maven-metadata.xml.sha1 b/samples-flatfoot/prebuilts0905/android/arch/persistence/room/migration/maven-metadata.xml.sha1
new file mode 100644
index 0000000..2c8a5a7
--- /dev/null
+++ b/samples-flatfoot/prebuilts0905/android/arch/persistence/room/migration/maven-metadata.xml.sha1
@@ -0,0 +1 @@
+cd88e7b77886db1e0b132f0e9e9a53cdb03970a3
\ No newline at end of file
diff --git a/samples-flatfoot/prebuilts0905/android/arch/persistence/room/runtime/1.0-SNAPSHOT/maven-metadata.xml b/samples-flatfoot/prebuilts0905/android/arch/persistence/room/runtime/1.0-SNAPSHOT/maven-metadata.xml
new file mode 100644
index 0000000..730bd12
--- /dev/null
+++ b/samples-flatfoot/prebuilts0905/android/arch/persistence/room/runtime/1.0-SNAPSHOT/maven-metadata.xml
@@ -0,0 +1,13 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<metadata>
+  <groupId>android.arch.persistence.room</groupId>
+  <artifactId>runtime</artifactId>
+  <version>1.0-SNAPSHOT</version>
+  <versioning>
+    <snapshot>
+      <timestamp>20170509.055326</timestamp>
+      <buildNumber>1</buildNumber>
+    </snapshot>
+    <lastUpdated>20170509055326</lastUpdated>
+  </versioning>
+</metadata>
diff --git a/samples-flatfoot/prebuilts0905/android/arch/persistence/room/runtime/1.0-SNAPSHOT/maven-metadata.xml.md5 b/samples-flatfoot/prebuilts0905/android/arch/persistence/room/runtime/1.0-SNAPSHOT/maven-metadata.xml.md5
new file mode 100644
index 0000000..fc02d49
--- /dev/null
+++ b/samples-flatfoot/prebuilts0905/android/arch/persistence/room/runtime/1.0-SNAPSHOT/maven-metadata.xml.md5
@@ -0,0 +1 @@
+a924de46a5dcc1837668bfa561d23711
\ No newline at end of file
diff --git a/samples-flatfoot/prebuilts0905/android/arch/persistence/room/runtime/1.0-SNAPSHOT/maven-metadata.xml.sha1 b/samples-flatfoot/prebuilts0905/android/arch/persistence/room/runtime/1.0-SNAPSHOT/maven-metadata.xml.sha1
new file mode 100644
index 0000000..b0aff48
--- /dev/null
+++ b/samples-flatfoot/prebuilts0905/android/arch/persistence/room/runtime/1.0-SNAPSHOT/maven-metadata.xml.sha1
@@ -0,0 +1 @@
+9104e562526a8f14c66bb9e98254b57ea80600d0
\ No newline at end of file
diff --git a/samples-flatfoot/prebuilts0905/android/arch/persistence/room/runtime/1.0-SNAPSHOT/runtime-1.0-20170509.055326-1-sources.jar b/samples-flatfoot/prebuilts0905/android/arch/persistence/room/runtime/1.0-SNAPSHOT/runtime-1.0-20170509.055326-1-sources.jar
new file mode 100644
index 0000000..f6ee59f
--- /dev/null
+++ b/samples-flatfoot/prebuilts0905/android/arch/persistence/room/runtime/1.0-SNAPSHOT/runtime-1.0-20170509.055326-1-sources.jar
Binary files differ
diff --git a/samples-flatfoot/prebuilts0905/android/arch/persistence/room/runtime/1.0-SNAPSHOT/runtime-1.0-20170509.055326-1-sources.jar.md5 b/samples-flatfoot/prebuilts0905/android/arch/persistence/room/runtime/1.0-SNAPSHOT/runtime-1.0-20170509.055326-1-sources.jar.md5
new file mode 100644
index 0000000..d40df09
--- /dev/null
+++ b/samples-flatfoot/prebuilts0905/android/arch/persistence/room/runtime/1.0-SNAPSHOT/runtime-1.0-20170509.055326-1-sources.jar.md5
@@ -0,0 +1 @@
+eda26e1ab785dc53971d889eb9845083
\ No newline at end of file
diff --git a/samples-flatfoot/prebuilts0905/android/arch/persistence/room/runtime/1.0-SNAPSHOT/runtime-1.0-20170509.055326-1-sources.jar.sha1 b/samples-flatfoot/prebuilts0905/android/arch/persistence/room/runtime/1.0-SNAPSHOT/runtime-1.0-20170509.055326-1-sources.jar.sha1
new file mode 100644
index 0000000..27bb421
--- /dev/null
+++ b/samples-flatfoot/prebuilts0905/android/arch/persistence/room/runtime/1.0-SNAPSHOT/runtime-1.0-20170509.055326-1-sources.jar.sha1
@@ -0,0 +1 @@
+e4e8c9823635c2322145356b70ef47f2cfc28ab1
\ No newline at end of file
diff --git a/samples-flatfoot/prebuilts0905/android/arch/persistence/room/runtime/1.0-SNAPSHOT/runtime-1.0-20170509.055326-1.aar b/samples-flatfoot/prebuilts0905/android/arch/persistence/room/runtime/1.0-SNAPSHOT/runtime-1.0-20170509.055326-1.aar
new file mode 100644
index 0000000..2379814
--- /dev/null
+++ b/samples-flatfoot/prebuilts0905/android/arch/persistence/room/runtime/1.0-SNAPSHOT/runtime-1.0-20170509.055326-1.aar
Binary files differ
diff --git a/samples-flatfoot/prebuilts0905/android/arch/persistence/room/runtime/1.0-SNAPSHOT/runtime-1.0-20170509.055326-1.aar.md5 b/samples-flatfoot/prebuilts0905/android/arch/persistence/room/runtime/1.0-SNAPSHOT/runtime-1.0-20170509.055326-1.aar.md5
new file mode 100644
index 0000000..1ec84fd
--- /dev/null
+++ b/samples-flatfoot/prebuilts0905/android/arch/persistence/room/runtime/1.0-SNAPSHOT/runtime-1.0-20170509.055326-1.aar.md5
@@ -0,0 +1 @@
+f0175ecb0f8d43aaaeb4d692fde7d116
\ No newline at end of file
diff --git a/samples-flatfoot/prebuilts0905/android/arch/persistence/room/runtime/1.0-SNAPSHOT/runtime-1.0-20170509.055326-1.aar.sha1 b/samples-flatfoot/prebuilts0905/android/arch/persistence/room/runtime/1.0-SNAPSHOT/runtime-1.0-20170509.055326-1.aar.sha1
new file mode 100644
index 0000000..d1e8e03
--- /dev/null
+++ b/samples-flatfoot/prebuilts0905/android/arch/persistence/room/runtime/1.0-SNAPSHOT/runtime-1.0-20170509.055326-1.aar.sha1
@@ -0,0 +1 @@
+a66782337fb073ec6f5f7697238c9346bc286532
\ No newline at end of file
diff --git a/samples-flatfoot/prebuilts0905/android/arch/persistence/room/runtime/1.0-SNAPSHOT/runtime-1.0-20170509.055326-1.pom b/samples-flatfoot/prebuilts0905/android/arch/persistence/room/runtime/1.0-SNAPSHOT/runtime-1.0-20170509.055326-1.pom
new file mode 100644
index 0000000..f36ae08
--- /dev/null
+++ b/samples-flatfoot/prebuilts0905/android/arch/persistence/room/runtime/1.0-SNAPSHOT/runtime-1.0-20170509.055326-1.pom
@@ -0,0 +1,41 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd" xmlns="http://maven.apache.org/POM/4.0.0"
+    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
+  <modelVersion>4.0.0</modelVersion>
+  <groupId>android.arch.persistence.room</groupId>
+  <artifactId>runtime</artifactId>
+  <version>1.0-SNAPSHOT</version>
+  <packaging>aar</packaging>
+  <dependencies>
+    <dependency>
+      <groupId>android.arch.persistence.room</groupId>
+      <artifactId>common</artifactId>
+      <version>1.0-SNAPSHOT</version>
+      <scope>compile</scope>
+    </dependency>
+    <dependency>
+      <groupId>android.arch.persistence.room</groupId>
+      <artifactId>support-db</artifactId>
+      <version>1.0-SNAPSHOT</version>
+      <scope>compile</scope>
+    </dependency>
+    <dependency>
+      <groupId>android.arch.persistence.room</groupId>
+      <artifactId>support-db-impl</artifactId>
+      <version>1.0-SNAPSHOT</version>
+      <scope>compile</scope>
+    </dependency>
+    <dependency>
+      <groupId>android.arch.core</groupId>
+      <artifactId>core</artifactId>
+      <version>1.0-SNAPSHOT</version>
+      <scope>compile</scope>
+    </dependency>
+    <dependency>
+      <groupId>com.android.support</groupId>
+      <artifactId>support-core-utils</artifactId>
+      <version>25.2.0</version>
+      <scope>compile</scope>
+    </dependency>
+  </dependencies>
+</project>
diff --git a/samples-flatfoot/prebuilts0905/android/arch/persistence/room/runtime/1.0-SNAPSHOT/runtime-1.0-20170509.055326-1.pom.md5 b/samples-flatfoot/prebuilts0905/android/arch/persistence/room/runtime/1.0-SNAPSHOT/runtime-1.0-20170509.055326-1.pom.md5
new file mode 100644
index 0000000..2b67aef
--- /dev/null
+++ b/samples-flatfoot/prebuilts0905/android/arch/persistence/room/runtime/1.0-SNAPSHOT/runtime-1.0-20170509.055326-1.pom.md5
@@ -0,0 +1 @@
+63a5441f7d834dca6869ca055581a475
\ No newline at end of file
diff --git a/samples-flatfoot/prebuilts0905/android/arch/persistence/room/runtime/1.0-SNAPSHOT/runtime-1.0-20170509.055326-1.pom.sha1 b/samples-flatfoot/prebuilts0905/android/arch/persistence/room/runtime/1.0-SNAPSHOT/runtime-1.0-20170509.055326-1.pom.sha1
new file mode 100644
index 0000000..f9dd7aa
--- /dev/null
+++ b/samples-flatfoot/prebuilts0905/android/arch/persistence/room/runtime/1.0-SNAPSHOT/runtime-1.0-20170509.055326-1.pom.sha1
@@ -0,0 +1 @@
+9b2b7d966e4a38c01ae60edd3f18b7cd54fba817
\ No newline at end of file
diff --git a/samples-flatfoot/prebuilts0905/android/arch/persistence/room/runtime/maven-metadata.xml b/samples-flatfoot/prebuilts0905/android/arch/persistence/room/runtime/maven-metadata.xml
new file mode 100644
index 0000000..b1872b1
--- /dev/null
+++ b/samples-flatfoot/prebuilts0905/android/arch/persistence/room/runtime/maven-metadata.xml
@@ -0,0 +1,11 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<metadata>
+  <groupId>android.arch.persistence.room</groupId>
+  <artifactId>runtime</artifactId>
+  <versioning>
+    <versions>
+      <version>1.0-SNAPSHOT</version>
+    </versions>
+    <lastUpdated>20170509055326</lastUpdated>
+  </versioning>
+</metadata>
diff --git a/samples-flatfoot/prebuilts0905/android/arch/persistence/room/runtime/maven-metadata.xml.md5 b/samples-flatfoot/prebuilts0905/android/arch/persistence/room/runtime/maven-metadata.xml.md5
new file mode 100644
index 0000000..9e5d9c2
--- /dev/null
+++ b/samples-flatfoot/prebuilts0905/android/arch/persistence/room/runtime/maven-metadata.xml.md5
@@ -0,0 +1 @@
+19721121b6e77919fb393e427498d077
\ No newline at end of file
diff --git a/samples-flatfoot/prebuilts0905/android/arch/persistence/room/runtime/maven-metadata.xml.sha1 b/samples-flatfoot/prebuilts0905/android/arch/persistence/room/runtime/maven-metadata.xml.sha1
new file mode 100644
index 0000000..2b87a99
--- /dev/null
+++ b/samples-flatfoot/prebuilts0905/android/arch/persistence/room/runtime/maven-metadata.xml.sha1
@@ -0,0 +1 @@
+993d09a7892477275a4fe8e66631c4588e08b510
\ No newline at end of file
diff --git a/samples-flatfoot/prebuilts0905/android/arch/persistence/room/rxjava2/1.0-SNAPSHOT/maven-metadata.xml b/samples-flatfoot/prebuilts0905/android/arch/persistence/room/rxjava2/1.0-SNAPSHOT/maven-metadata.xml
new file mode 100644
index 0000000..2c5971f
--- /dev/null
+++ b/samples-flatfoot/prebuilts0905/android/arch/persistence/room/rxjava2/1.0-SNAPSHOT/maven-metadata.xml
@@ -0,0 +1,13 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<metadata>
+  <groupId>android.arch.persistence.room</groupId>
+  <artifactId>rxjava2</artifactId>
+  <version>1.0-SNAPSHOT</version>
+  <versioning>
+    <snapshot>
+      <timestamp>20170509.055327</timestamp>
+      <buildNumber>1</buildNumber>
+    </snapshot>
+    <lastUpdated>20170509055327</lastUpdated>
+  </versioning>
+</metadata>
diff --git a/samples-flatfoot/prebuilts0905/android/arch/persistence/room/rxjava2/1.0-SNAPSHOT/maven-metadata.xml.md5 b/samples-flatfoot/prebuilts0905/android/arch/persistence/room/rxjava2/1.0-SNAPSHOT/maven-metadata.xml.md5
new file mode 100644
index 0000000..e4459d4
--- /dev/null
+++ b/samples-flatfoot/prebuilts0905/android/arch/persistence/room/rxjava2/1.0-SNAPSHOT/maven-metadata.xml.md5
@@ -0,0 +1 @@
+c0a13a54362f5390ee372be302cbcbeb
\ No newline at end of file
diff --git a/samples-flatfoot/prebuilts0905/android/arch/persistence/room/rxjava2/1.0-SNAPSHOT/maven-metadata.xml.sha1 b/samples-flatfoot/prebuilts0905/android/arch/persistence/room/rxjava2/1.0-SNAPSHOT/maven-metadata.xml.sha1
new file mode 100644
index 0000000..b2c0179
--- /dev/null
+++ b/samples-flatfoot/prebuilts0905/android/arch/persistence/room/rxjava2/1.0-SNAPSHOT/maven-metadata.xml.sha1
@@ -0,0 +1 @@
+a22e62e654b051d3747bea4b571eab0ce8a7f74c
\ No newline at end of file
diff --git a/samples-flatfoot/prebuilts0905/android/arch/persistence/room/rxjava2/1.0-SNAPSHOT/rxjava2-1.0-20170509.055327-1-sources.jar b/samples-flatfoot/prebuilts0905/android/arch/persistence/room/rxjava2/1.0-SNAPSHOT/rxjava2-1.0-20170509.055327-1-sources.jar
new file mode 100644
index 0000000..01fcbf2
--- /dev/null
+++ b/samples-flatfoot/prebuilts0905/android/arch/persistence/room/rxjava2/1.0-SNAPSHOT/rxjava2-1.0-20170509.055327-1-sources.jar
Binary files differ
diff --git a/samples-flatfoot/prebuilts0905/android/arch/persistence/room/rxjava2/1.0-SNAPSHOT/rxjava2-1.0-20170509.055327-1-sources.jar.md5 b/samples-flatfoot/prebuilts0905/android/arch/persistence/room/rxjava2/1.0-SNAPSHOT/rxjava2-1.0-20170509.055327-1-sources.jar.md5
new file mode 100644
index 0000000..3054f51
--- /dev/null
+++ b/samples-flatfoot/prebuilts0905/android/arch/persistence/room/rxjava2/1.0-SNAPSHOT/rxjava2-1.0-20170509.055327-1-sources.jar.md5
@@ -0,0 +1 @@
+35d73b25d37fcee1a93968ec26b3cff5
\ No newline at end of file
diff --git a/samples-flatfoot/prebuilts0905/android/arch/persistence/room/rxjava2/1.0-SNAPSHOT/rxjava2-1.0-20170509.055327-1-sources.jar.sha1 b/samples-flatfoot/prebuilts0905/android/arch/persistence/room/rxjava2/1.0-SNAPSHOT/rxjava2-1.0-20170509.055327-1-sources.jar.sha1
new file mode 100644
index 0000000..5182d8c
--- /dev/null
+++ b/samples-flatfoot/prebuilts0905/android/arch/persistence/room/rxjava2/1.0-SNAPSHOT/rxjava2-1.0-20170509.055327-1-sources.jar.sha1
@@ -0,0 +1 @@
+4019a6fb4e91c18e57c616f7757b1416cfd3dbe4
\ No newline at end of file
diff --git a/samples-flatfoot/prebuilts0905/android/arch/persistence/room/rxjava2/1.0-SNAPSHOT/rxjava2-1.0-20170509.055327-1.aar b/samples-flatfoot/prebuilts0905/android/arch/persistence/room/rxjava2/1.0-SNAPSHOT/rxjava2-1.0-20170509.055327-1.aar
new file mode 100644
index 0000000..ff6d027
--- /dev/null
+++ b/samples-flatfoot/prebuilts0905/android/arch/persistence/room/rxjava2/1.0-SNAPSHOT/rxjava2-1.0-20170509.055327-1.aar
Binary files differ
diff --git a/samples-flatfoot/prebuilts0905/android/arch/persistence/room/rxjava2/1.0-SNAPSHOT/rxjava2-1.0-20170509.055327-1.aar.md5 b/samples-flatfoot/prebuilts0905/android/arch/persistence/room/rxjava2/1.0-SNAPSHOT/rxjava2-1.0-20170509.055327-1.aar.md5
new file mode 100644
index 0000000..22d1715
--- /dev/null
+++ b/samples-flatfoot/prebuilts0905/android/arch/persistence/room/rxjava2/1.0-SNAPSHOT/rxjava2-1.0-20170509.055327-1.aar.md5
@@ -0,0 +1 @@
+5a829c47d703301417cc6cdfb5fbd59b
\ No newline at end of file
diff --git a/samples-flatfoot/prebuilts0905/android/arch/persistence/room/rxjava2/1.0-SNAPSHOT/rxjava2-1.0-20170509.055327-1.aar.sha1 b/samples-flatfoot/prebuilts0905/android/arch/persistence/room/rxjava2/1.0-SNAPSHOT/rxjava2-1.0-20170509.055327-1.aar.sha1
new file mode 100644
index 0000000..5a0f774
--- /dev/null
+++ b/samples-flatfoot/prebuilts0905/android/arch/persistence/room/rxjava2/1.0-SNAPSHOT/rxjava2-1.0-20170509.055327-1.aar.sha1
@@ -0,0 +1 @@
+c0ab291d1694054050f731b9dbff531a70e16062
\ No newline at end of file
diff --git a/samples-flatfoot/prebuilts0905/android/arch/persistence/room/rxjava2/1.0-SNAPSHOT/rxjava2-1.0-20170509.055327-1.pom b/samples-flatfoot/prebuilts0905/android/arch/persistence/room/rxjava2/1.0-SNAPSHOT/rxjava2-1.0-20170509.055327-1.pom
new file mode 100644
index 0000000..a874342
--- /dev/null
+++ b/samples-flatfoot/prebuilts0905/android/arch/persistence/room/rxjava2/1.0-SNAPSHOT/rxjava2-1.0-20170509.055327-1.pom
@@ -0,0 +1,41 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd" xmlns="http://maven.apache.org/POM/4.0.0"
+    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
+  <modelVersion>4.0.0</modelVersion>
+  <groupId>android.arch.persistence.room</groupId>
+  <artifactId>rxjava2</artifactId>
+  <version>1.0-SNAPSHOT</version>
+  <packaging>aar</packaging>
+  <dependencies>
+    <dependency>
+      <groupId>android.arch.persistence.room</groupId>
+      <artifactId>common</artifactId>
+      <version>1.0-SNAPSHOT</version>
+      <scope>compile</scope>
+    </dependency>
+    <dependency>
+      <groupId>android.arch.persistence.room</groupId>
+      <artifactId>runtime</artifactId>
+      <version>1.0-SNAPSHOT</version>
+      <scope>compile</scope>
+    </dependency>
+    <dependency>
+      <groupId>android.arch.core</groupId>
+      <artifactId>core</artifactId>
+      <version>1.0-SNAPSHOT</version>
+      <scope>compile</scope>
+    </dependency>
+    <dependency>
+      <groupId>com.android.support</groupId>
+      <artifactId>support-core-utils</artifactId>
+      <version>25.2.0</version>
+      <scope>compile</scope>
+    </dependency>
+    <dependency>
+      <groupId>io.reactivex.rxjava2</groupId>
+      <artifactId>rxjava</artifactId>
+      <version>2.0.6</version>
+      <scope>compile</scope>
+    </dependency>
+  </dependencies>
+</project>
diff --git a/samples-flatfoot/prebuilts0905/android/arch/persistence/room/rxjava2/1.0-SNAPSHOT/rxjava2-1.0-20170509.055327-1.pom.md5 b/samples-flatfoot/prebuilts0905/android/arch/persistence/room/rxjava2/1.0-SNAPSHOT/rxjava2-1.0-20170509.055327-1.pom.md5
new file mode 100644
index 0000000..35a1d84
--- /dev/null
+++ b/samples-flatfoot/prebuilts0905/android/arch/persistence/room/rxjava2/1.0-SNAPSHOT/rxjava2-1.0-20170509.055327-1.pom.md5
@@ -0,0 +1 @@
+93d2c8e208b502d109ceb9f643cb45f9
\ No newline at end of file
diff --git a/samples-flatfoot/prebuilts0905/android/arch/persistence/room/rxjava2/1.0-SNAPSHOT/rxjava2-1.0-20170509.055327-1.pom.sha1 b/samples-flatfoot/prebuilts0905/android/arch/persistence/room/rxjava2/1.0-SNAPSHOT/rxjava2-1.0-20170509.055327-1.pom.sha1
new file mode 100644
index 0000000..db0de3c
--- /dev/null
+++ b/samples-flatfoot/prebuilts0905/android/arch/persistence/room/rxjava2/1.0-SNAPSHOT/rxjava2-1.0-20170509.055327-1.pom.sha1
@@ -0,0 +1 @@
+dd0eb45799347fdb34ecc954aac657c3171aee92
\ No newline at end of file
diff --git a/samples-flatfoot/prebuilts0905/android/arch/persistence/room/rxjava2/maven-metadata.xml b/samples-flatfoot/prebuilts0905/android/arch/persistence/room/rxjava2/maven-metadata.xml
new file mode 100644
index 0000000..8715932
--- /dev/null
+++ b/samples-flatfoot/prebuilts0905/android/arch/persistence/room/rxjava2/maven-metadata.xml
@@ -0,0 +1,11 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<metadata>
+  <groupId>android.arch.persistence.room</groupId>
+  <artifactId>rxjava2</artifactId>
+  <versioning>
+    <versions>
+      <version>1.0-SNAPSHOT</version>
+    </versions>
+    <lastUpdated>20170509055327</lastUpdated>
+  </versioning>
+</metadata>
diff --git a/samples-flatfoot/prebuilts0905/android/arch/persistence/room/rxjava2/maven-metadata.xml.md5 b/samples-flatfoot/prebuilts0905/android/arch/persistence/room/rxjava2/maven-metadata.xml.md5
new file mode 100644
index 0000000..5fc31ef
--- /dev/null
+++ b/samples-flatfoot/prebuilts0905/android/arch/persistence/room/rxjava2/maven-metadata.xml.md5
@@ -0,0 +1 @@
+7c48385b26704ac646c7bf9cd8a5bb52
\ No newline at end of file
diff --git a/samples-flatfoot/prebuilts0905/android/arch/persistence/room/rxjava2/maven-metadata.xml.sha1 b/samples-flatfoot/prebuilts0905/android/arch/persistence/room/rxjava2/maven-metadata.xml.sha1
new file mode 100644
index 0000000..77f2cdd
--- /dev/null
+++ b/samples-flatfoot/prebuilts0905/android/arch/persistence/room/rxjava2/maven-metadata.xml.sha1
@@ -0,0 +1 @@
+8ba90dedbd7600671eb67ac40259103b2cac38af
\ No newline at end of file
diff --git a/samples-flatfoot/prebuilts0905/android/arch/persistence/room/support-db-impl/1.0-SNAPSHOT/maven-metadata.xml b/samples-flatfoot/prebuilts0905/android/arch/persistence/room/support-db-impl/1.0-SNAPSHOT/maven-metadata.xml
new file mode 100644
index 0000000..40eb680
--- /dev/null
+++ b/samples-flatfoot/prebuilts0905/android/arch/persistence/room/support-db-impl/1.0-SNAPSHOT/maven-metadata.xml
@@ -0,0 +1,13 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<metadata>
+  <groupId>android.arch.persistence.room</groupId>
+  <artifactId>support-db-impl</artifactId>
+  <version>1.0-SNAPSHOT</version>
+  <versioning>
+    <snapshot>
+      <timestamp>20170509.055321</timestamp>
+      <buildNumber>1</buildNumber>
+    </snapshot>
+    <lastUpdated>20170509055321</lastUpdated>
+  </versioning>
+</metadata>
diff --git a/samples-flatfoot/prebuilts0905/android/arch/persistence/room/support-db-impl/1.0-SNAPSHOT/maven-metadata.xml.md5 b/samples-flatfoot/prebuilts0905/android/arch/persistence/room/support-db-impl/1.0-SNAPSHOT/maven-metadata.xml.md5
new file mode 100644
index 0000000..9365695
--- /dev/null
+++ b/samples-flatfoot/prebuilts0905/android/arch/persistence/room/support-db-impl/1.0-SNAPSHOT/maven-metadata.xml.md5
@@ -0,0 +1 @@
+3bc629486eaa6d2cae6abfcad5265a22
\ No newline at end of file
diff --git a/samples-flatfoot/prebuilts0905/android/arch/persistence/room/support-db-impl/1.0-SNAPSHOT/maven-metadata.xml.sha1 b/samples-flatfoot/prebuilts0905/android/arch/persistence/room/support-db-impl/1.0-SNAPSHOT/maven-metadata.xml.sha1
new file mode 100644
index 0000000..b5f569b
--- /dev/null
+++ b/samples-flatfoot/prebuilts0905/android/arch/persistence/room/support-db-impl/1.0-SNAPSHOT/maven-metadata.xml.sha1
@@ -0,0 +1 @@
+c6bb4877462ab4f0e61ae9d128e88f8b91d016e5
\ No newline at end of file
diff --git a/samples-flatfoot/prebuilts0905/android/arch/persistence/room/support-db-impl/1.0-SNAPSHOT/support-db-impl-1.0-20170509.055321-1-sources.jar b/samples-flatfoot/prebuilts0905/android/arch/persistence/room/support-db-impl/1.0-SNAPSHOT/support-db-impl-1.0-20170509.055321-1-sources.jar
new file mode 100644
index 0000000..7f1a590
--- /dev/null
+++ b/samples-flatfoot/prebuilts0905/android/arch/persistence/room/support-db-impl/1.0-SNAPSHOT/support-db-impl-1.0-20170509.055321-1-sources.jar
Binary files differ
diff --git a/samples-flatfoot/prebuilts0905/android/arch/persistence/room/support-db-impl/1.0-SNAPSHOT/support-db-impl-1.0-20170509.055321-1-sources.jar.md5 b/samples-flatfoot/prebuilts0905/android/arch/persistence/room/support-db-impl/1.0-SNAPSHOT/support-db-impl-1.0-20170509.055321-1-sources.jar.md5
new file mode 100644
index 0000000..20e510b
--- /dev/null
+++ b/samples-flatfoot/prebuilts0905/android/arch/persistence/room/support-db-impl/1.0-SNAPSHOT/support-db-impl-1.0-20170509.055321-1-sources.jar.md5
@@ -0,0 +1 @@
+55ed9edfb94fb93ec6472f0b8e0711b8
\ No newline at end of file
diff --git a/samples-flatfoot/prebuilts0905/android/arch/persistence/room/support-db-impl/1.0-SNAPSHOT/support-db-impl-1.0-20170509.055321-1-sources.jar.sha1 b/samples-flatfoot/prebuilts0905/android/arch/persistence/room/support-db-impl/1.0-SNAPSHOT/support-db-impl-1.0-20170509.055321-1-sources.jar.sha1
new file mode 100644
index 0000000..656af8d
--- /dev/null
+++ b/samples-flatfoot/prebuilts0905/android/arch/persistence/room/support-db-impl/1.0-SNAPSHOT/support-db-impl-1.0-20170509.055321-1-sources.jar.sha1
@@ -0,0 +1 @@
+8c84ee3e91cce6ccf284bc94d4472714092bc98d
\ No newline at end of file
diff --git a/samples-flatfoot/prebuilts0905/android/arch/persistence/room/support-db-impl/1.0-SNAPSHOT/support-db-impl-1.0-20170509.055321-1.aar b/samples-flatfoot/prebuilts0905/android/arch/persistence/room/support-db-impl/1.0-SNAPSHOT/support-db-impl-1.0-20170509.055321-1.aar
new file mode 100644
index 0000000..d079677
--- /dev/null
+++ b/samples-flatfoot/prebuilts0905/android/arch/persistence/room/support-db-impl/1.0-SNAPSHOT/support-db-impl-1.0-20170509.055321-1.aar
Binary files differ
diff --git a/samples-flatfoot/prebuilts0905/android/arch/persistence/room/support-db-impl/1.0-SNAPSHOT/support-db-impl-1.0-20170509.055321-1.aar.md5 b/samples-flatfoot/prebuilts0905/android/arch/persistence/room/support-db-impl/1.0-SNAPSHOT/support-db-impl-1.0-20170509.055321-1.aar.md5
new file mode 100644
index 0000000..31fee99
--- /dev/null
+++ b/samples-flatfoot/prebuilts0905/android/arch/persistence/room/support-db-impl/1.0-SNAPSHOT/support-db-impl-1.0-20170509.055321-1.aar.md5
@@ -0,0 +1 @@
+245cae9051e31d798357f66330982b5f
\ No newline at end of file
diff --git a/samples-flatfoot/prebuilts0905/android/arch/persistence/room/support-db-impl/1.0-SNAPSHOT/support-db-impl-1.0-20170509.055321-1.aar.sha1 b/samples-flatfoot/prebuilts0905/android/arch/persistence/room/support-db-impl/1.0-SNAPSHOT/support-db-impl-1.0-20170509.055321-1.aar.sha1
new file mode 100644
index 0000000..4161415
--- /dev/null
+++ b/samples-flatfoot/prebuilts0905/android/arch/persistence/room/support-db-impl/1.0-SNAPSHOT/support-db-impl-1.0-20170509.055321-1.aar.sha1
@@ -0,0 +1 @@
+1fdafb1d828ce170ca0f11c3ddb293bcf5efce0f
\ No newline at end of file
diff --git a/samples-flatfoot/prebuilts0905/android/arch/persistence/room/support-db-impl/1.0-SNAPSHOT/support-db-impl-1.0-20170509.055321-1.pom b/samples-flatfoot/prebuilts0905/android/arch/persistence/room/support-db-impl/1.0-SNAPSHOT/support-db-impl-1.0-20170509.055321-1.pom
new file mode 100644
index 0000000..73afcf1
--- /dev/null
+++ b/samples-flatfoot/prebuilts0905/android/arch/persistence/room/support-db-impl/1.0-SNAPSHOT/support-db-impl-1.0-20170509.055321-1.pom
@@ -0,0 +1,23 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd" xmlns="http://maven.apache.org/POM/4.0.0"
+    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
+  <modelVersion>4.0.0</modelVersion>
+  <groupId>android.arch.persistence.room</groupId>
+  <artifactId>support-db-impl</artifactId>
+  <version>1.0-SNAPSHOT</version>
+  <packaging>aar</packaging>
+  <dependencies>
+    <dependency>
+      <groupId>com.android.support</groupId>
+      <artifactId>support-annotations</artifactId>
+      <version>25.2.0</version>
+      <scope>compile</scope>
+    </dependency>
+    <dependency>
+      <groupId>android.arch.persistence.room</groupId>
+      <artifactId>support-db</artifactId>
+      <version>1.0-SNAPSHOT</version>
+      <scope>compile</scope>
+    </dependency>
+  </dependencies>
+</project>
diff --git a/samples-flatfoot/prebuilts0905/android/arch/persistence/room/support-db-impl/1.0-SNAPSHOT/support-db-impl-1.0-20170509.055321-1.pom.md5 b/samples-flatfoot/prebuilts0905/android/arch/persistence/room/support-db-impl/1.0-SNAPSHOT/support-db-impl-1.0-20170509.055321-1.pom.md5
new file mode 100644
index 0000000..d5372bd
--- /dev/null
+++ b/samples-flatfoot/prebuilts0905/android/arch/persistence/room/support-db-impl/1.0-SNAPSHOT/support-db-impl-1.0-20170509.055321-1.pom.md5
@@ -0,0 +1 @@
+380065e2dc039d8d34076288bb2ea938
\ No newline at end of file
diff --git a/samples-flatfoot/prebuilts0905/android/arch/persistence/room/support-db-impl/1.0-SNAPSHOT/support-db-impl-1.0-20170509.055321-1.pom.sha1 b/samples-flatfoot/prebuilts0905/android/arch/persistence/room/support-db-impl/1.0-SNAPSHOT/support-db-impl-1.0-20170509.055321-1.pom.sha1
new file mode 100644
index 0000000..9559b0e
--- /dev/null
+++ b/samples-flatfoot/prebuilts0905/android/arch/persistence/room/support-db-impl/1.0-SNAPSHOT/support-db-impl-1.0-20170509.055321-1.pom.sha1
@@ -0,0 +1 @@
+c532878901a9aea9a5524ae24f14a065c5a3b87e
\ No newline at end of file
diff --git a/samples-flatfoot/prebuilts0905/android/arch/persistence/room/support-db-impl/maven-metadata.xml b/samples-flatfoot/prebuilts0905/android/arch/persistence/room/support-db-impl/maven-metadata.xml
new file mode 100644
index 0000000..965ab38
--- /dev/null
+++ b/samples-flatfoot/prebuilts0905/android/arch/persistence/room/support-db-impl/maven-metadata.xml
@@ -0,0 +1,11 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<metadata>
+  <groupId>android.arch.persistence.room</groupId>
+  <artifactId>support-db-impl</artifactId>
+  <versioning>
+    <versions>
+      <version>1.0-SNAPSHOT</version>
+    </versions>
+    <lastUpdated>20170509055321</lastUpdated>
+  </versioning>
+</metadata>
diff --git a/samples-flatfoot/prebuilts0905/android/arch/persistence/room/support-db-impl/maven-metadata.xml.md5 b/samples-flatfoot/prebuilts0905/android/arch/persistence/room/support-db-impl/maven-metadata.xml.md5
new file mode 100644
index 0000000..ab89ce3
--- /dev/null
+++ b/samples-flatfoot/prebuilts0905/android/arch/persistence/room/support-db-impl/maven-metadata.xml.md5
@@ -0,0 +1 @@
+fd51ff5d8d3680ec184c53d3b91d0ed1
\ No newline at end of file
diff --git a/samples-flatfoot/prebuilts0905/android/arch/persistence/room/support-db-impl/maven-metadata.xml.sha1 b/samples-flatfoot/prebuilts0905/android/arch/persistence/room/support-db-impl/maven-metadata.xml.sha1
new file mode 100644
index 0000000..c72819f
--- /dev/null
+++ b/samples-flatfoot/prebuilts0905/android/arch/persistence/room/support-db-impl/maven-metadata.xml.sha1
@@ -0,0 +1 @@
+a2d5a89cb59f6fc89cba73e91b985b344afd87f4
\ No newline at end of file
diff --git a/samples-flatfoot/prebuilts0905/android/arch/persistence/room/support-db/1.0-SNAPSHOT/maven-metadata.xml b/samples-flatfoot/prebuilts0905/android/arch/persistence/room/support-db/1.0-SNAPSHOT/maven-metadata.xml
new file mode 100644
index 0000000..bf42d23
--- /dev/null
+++ b/samples-flatfoot/prebuilts0905/android/arch/persistence/room/support-db/1.0-SNAPSHOT/maven-metadata.xml
@@ -0,0 +1,13 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<metadata>
+  <groupId>android.arch.persistence.room</groupId>
+  <artifactId>support-db</artifactId>
+  <version>1.0-SNAPSHOT</version>
+  <versioning>
+    <snapshot>
+      <timestamp>20170509.055321</timestamp>
+      <buildNumber>1</buildNumber>
+    </snapshot>
+    <lastUpdated>20170509055321</lastUpdated>
+  </versioning>
+</metadata>
diff --git a/samples-flatfoot/prebuilts0905/android/arch/persistence/room/support-db/1.0-SNAPSHOT/maven-metadata.xml.md5 b/samples-flatfoot/prebuilts0905/android/arch/persistence/room/support-db/1.0-SNAPSHOT/maven-metadata.xml.md5
new file mode 100644
index 0000000..873f0bc
--- /dev/null
+++ b/samples-flatfoot/prebuilts0905/android/arch/persistence/room/support-db/1.0-SNAPSHOT/maven-metadata.xml.md5
@@ -0,0 +1 @@
+2cf9e4ddbd0f858a15e07ac3355aeb24
\ No newline at end of file
diff --git a/samples-flatfoot/prebuilts0905/android/arch/persistence/room/support-db/1.0-SNAPSHOT/maven-metadata.xml.sha1 b/samples-flatfoot/prebuilts0905/android/arch/persistence/room/support-db/1.0-SNAPSHOT/maven-metadata.xml.sha1
new file mode 100644
index 0000000..9134405
--- /dev/null
+++ b/samples-flatfoot/prebuilts0905/android/arch/persistence/room/support-db/1.0-SNAPSHOT/maven-metadata.xml.sha1
@@ -0,0 +1 @@
+5f2a26acd9607febe58a18cce0265f2ca4c880aa
\ No newline at end of file
diff --git a/samples-flatfoot/prebuilts0905/android/arch/persistence/room/support-db/1.0-SNAPSHOT/support-db-1.0-20170509.055321-1-sources.jar b/samples-flatfoot/prebuilts0905/android/arch/persistence/room/support-db/1.0-SNAPSHOT/support-db-1.0-20170509.055321-1-sources.jar
new file mode 100644
index 0000000..52ad39a
--- /dev/null
+++ b/samples-flatfoot/prebuilts0905/android/arch/persistence/room/support-db/1.0-SNAPSHOT/support-db-1.0-20170509.055321-1-sources.jar
Binary files differ
diff --git a/samples-flatfoot/prebuilts0905/android/arch/persistence/room/support-db/1.0-SNAPSHOT/support-db-1.0-20170509.055321-1-sources.jar.md5 b/samples-flatfoot/prebuilts0905/android/arch/persistence/room/support-db/1.0-SNAPSHOT/support-db-1.0-20170509.055321-1-sources.jar.md5
new file mode 100644
index 0000000..16b3b27
--- /dev/null
+++ b/samples-flatfoot/prebuilts0905/android/arch/persistence/room/support-db/1.0-SNAPSHOT/support-db-1.0-20170509.055321-1-sources.jar.md5
@@ -0,0 +1 @@
+4266c636eb39b348d7d91e4dcce2ddf0
\ No newline at end of file
diff --git a/samples-flatfoot/prebuilts0905/android/arch/persistence/room/support-db/1.0-SNAPSHOT/support-db-1.0-20170509.055321-1-sources.jar.sha1 b/samples-flatfoot/prebuilts0905/android/arch/persistence/room/support-db/1.0-SNAPSHOT/support-db-1.0-20170509.055321-1-sources.jar.sha1
new file mode 100644
index 0000000..212c5f6
--- /dev/null
+++ b/samples-flatfoot/prebuilts0905/android/arch/persistence/room/support-db/1.0-SNAPSHOT/support-db-1.0-20170509.055321-1-sources.jar.sha1
@@ -0,0 +1 @@
+a9af60a542e8a4cb28d6983e87cb5e65c84bce33
\ No newline at end of file
diff --git a/samples-flatfoot/prebuilts0905/android/arch/persistence/room/support-db/1.0-SNAPSHOT/support-db-1.0-20170509.055321-1.aar b/samples-flatfoot/prebuilts0905/android/arch/persistence/room/support-db/1.0-SNAPSHOT/support-db-1.0-20170509.055321-1.aar
new file mode 100644
index 0000000..ae6fd22
--- /dev/null
+++ b/samples-flatfoot/prebuilts0905/android/arch/persistence/room/support-db/1.0-SNAPSHOT/support-db-1.0-20170509.055321-1.aar
Binary files differ
diff --git a/samples-flatfoot/prebuilts0905/android/arch/persistence/room/support-db/1.0-SNAPSHOT/support-db-1.0-20170509.055321-1.aar.md5 b/samples-flatfoot/prebuilts0905/android/arch/persistence/room/support-db/1.0-SNAPSHOT/support-db-1.0-20170509.055321-1.aar.md5
new file mode 100644
index 0000000..9b46fb8
--- /dev/null
+++ b/samples-flatfoot/prebuilts0905/android/arch/persistence/room/support-db/1.0-SNAPSHOT/support-db-1.0-20170509.055321-1.aar.md5
@@ -0,0 +1 @@
+aff2c60fe9ae9879d3aba2b2d74e426b
\ No newline at end of file
diff --git a/samples-flatfoot/prebuilts0905/android/arch/persistence/room/support-db/1.0-SNAPSHOT/support-db-1.0-20170509.055321-1.aar.sha1 b/samples-flatfoot/prebuilts0905/android/arch/persistence/room/support-db/1.0-SNAPSHOT/support-db-1.0-20170509.055321-1.aar.sha1
new file mode 100644
index 0000000..e43f29e
--- /dev/null
+++ b/samples-flatfoot/prebuilts0905/android/arch/persistence/room/support-db/1.0-SNAPSHOT/support-db-1.0-20170509.055321-1.aar.sha1
@@ -0,0 +1 @@
+3c485b7a86a6cccbf98b6f330bbf7a1409798f16
\ No newline at end of file
diff --git a/samples-flatfoot/prebuilts0905/android/arch/persistence/room/support-db/1.0-SNAPSHOT/support-db-1.0-20170509.055321-1.pom b/samples-flatfoot/prebuilts0905/android/arch/persistence/room/support-db/1.0-SNAPSHOT/support-db-1.0-20170509.055321-1.pom
new file mode 100644
index 0000000..16e3450
--- /dev/null
+++ b/samples-flatfoot/prebuilts0905/android/arch/persistence/room/support-db/1.0-SNAPSHOT/support-db-1.0-20170509.055321-1.pom
@@ -0,0 +1,17 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd" xmlns="http://maven.apache.org/POM/4.0.0"
+    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
+  <modelVersion>4.0.0</modelVersion>
+  <groupId>android.arch.persistence.room</groupId>
+  <artifactId>support-db</artifactId>
+  <version>1.0-SNAPSHOT</version>
+  <packaging>aar</packaging>
+  <dependencies>
+    <dependency>
+      <groupId>com.android.support</groupId>
+      <artifactId>support-annotations</artifactId>
+      <version>25.2.0</version>
+      <scope>compile</scope>
+    </dependency>
+  </dependencies>
+</project>
diff --git a/samples-flatfoot/prebuilts0905/android/arch/persistence/room/support-db/1.0-SNAPSHOT/support-db-1.0-20170509.055321-1.pom.md5 b/samples-flatfoot/prebuilts0905/android/arch/persistence/room/support-db/1.0-SNAPSHOT/support-db-1.0-20170509.055321-1.pom.md5
new file mode 100644
index 0000000..b3cc2ff
--- /dev/null
+++ b/samples-flatfoot/prebuilts0905/android/arch/persistence/room/support-db/1.0-SNAPSHOT/support-db-1.0-20170509.055321-1.pom.md5
@@ -0,0 +1 @@
+efe1c96944a927073adda14ff86c2135
\ No newline at end of file
diff --git a/samples-flatfoot/prebuilts0905/android/arch/persistence/room/support-db/1.0-SNAPSHOT/support-db-1.0-20170509.055321-1.pom.sha1 b/samples-flatfoot/prebuilts0905/android/arch/persistence/room/support-db/1.0-SNAPSHOT/support-db-1.0-20170509.055321-1.pom.sha1
new file mode 100644
index 0000000..a180ef3
--- /dev/null
+++ b/samples-flatfoot/prebuilts0905/android/arch/persistence/room/support-db/1.0-SNAPSHOT/support-db-1.0-20170509.055321-1.pom.sha1
@@ -0,0 +1 @@
+b85080854ab0c7856175b5bc235f07392df97b77
\ No newline at end of file
diff --git a/samples-flatfoot/prebuilts0905/android/arch/persistence/room/support-db/maven-metadata.xml b/samples-flatfoot/prebuilts0905/android/arch/persistence/room/support-db/maven-metadata.xml
new file mode 100644
index 0000000..f2bc7f7
--- /dev/null
+++ b/samples-flatfoot/prebuilts0905/android/arch/persistence/room/support-db/maven-metadata.xml
@@ -0,0 +1,11 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<metadata>
+  <groupId>android.arch.persistence.room</groupId>
+  <artifactId>support-db</artifactId>
+  <versioning>
+    <versions>
+      <version>1.0-SNAPSHOT</version>
+    </versions>
+    <lastUpdated>20170509055321</lastUpdated>
+  </versioning>
+</metadata>
diff --git a/samples-flatfoot/prebuilts0905/android/arch/persistence/room/support-db/maven-metadata.xml.md5 b/samples-flatfoot/prebuilts0905/android/arch/persistence/room/support-db/maven-metadata.xml.md5
new file mode 100644
index 0000000..0901e08
--- /dev/null
+++ b/samples-flatfoot/prebuilts0905/android/arch/persistence/room/support-db/maven-metadata.xml.md5
@@ -0,0 +1 @@
+fa59bfa211d652dcc19b2e663c68ebc4
\ No newline at end of file
diff --git a/samples-flatfoot/prebuilts0905/android/arch/persistence/room/support-db/maven-metadata.xml.sha1 b/samples-flatfoot/prebuilts0905/android/arch/persistence/room/support-db/maven-metadata.xml.sha1
new file mode 100644
index 0000000..7432573
--- /dev/null
+++ b/samples-flatfoot/prebuilts0905/android/arch/persistence/room/support-db/maven-metadata.xml.sha1
@@ -0,0 +1 @@
+2c3c2f524d551508d7adc3f99c3c0a26c08ee9d8
\ No newline at end of file
diff --git a/samples-flatfoot/prebuilts0905/android/arch/persistence/room/testing/1.0-SNAPSHOT/maven-metadata.xml b/samples-flatfoot/prebuilts0905/android/arch/persistence/room/testing/1.0-SNAPSHOT/maven-metadata.xml
new file mode 100644
index 0000000..b73482a
--- /dev/null
+++ b/samples-flatfoot/prebuilts0905/android/arch/persistence/room/testing/1.0-SNAPSHOT/maven-metadata.xml
@@ -0,0 +1,13 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<metadata>
+  <groupId>android.arch.persistence.room</groupId>
+  <artifactId>testing</artifactId>
+  <version>1.0-SNAPSHOT</version>
+  <versioning>
+    <snapshot>
+      <timestamp>20170509.055327</timestamp>
+      <buildNumber>1</buildNumber>
+    </snapshot>
+    <lastUpdated>20170509055327</lastUpdated>
+  </versioning>
+</metadata>
diff --git a/samples-flatfoot/prebuilts0905/android/arch/persistence/room/testing/1.0-SNAPSHOT/maven-metadata.xml.md5 b/samples-flatfoot/prebuilts0905/android/arch/persistence/room/testing/1.0-SNAPSHOT/maven-metadata.xml.md5
new file mode 100644
index 0000000..bdad56c
--- /dev/null
+++ b/samples-flatfoot/prebuilts0905/android/arch/persistence/room/testing/1.0-SNAPSHOT/maven-metadata.xml.md5
@@ -0,0 +1 @@
+e2d9eb39804daee0510063402d3657e6
\ No newline at end of file
diff --git a/samples-flatfoot/prebuilts0905/android/arch/persistence/room/testing/1.0-SNAPSHOT/maven-metadata.xml.sha1 b/samples-flatfoot/prebuilts0905/android/arch/persistence/room/testing/1.0-SNAPSHOT/maven-metadata.xml.sha1
new file mode 100644
index 0000000..56ed4cc
--- /dev/null
+++ b/samples-flatfoot/prebuilts0905/android/arch/persistence/room/testing/1.0-SNAPSHOT/maven-metadata.xml.sha1
@@ -0,0 +1 @@
+0c604096877dbc6ef6c947217335895115690568
\ No newline at end of file
diff --git a/samples-flatfoot/prebuilts0905/android/arch/persistence/room/testing/1.0-SNAPSHOT/testing-1.0-20170509.055327-1-sources.jar b/samples-flatfoot/prebuilts0905/android/arch/persistence/room/testing/1.0-SNAPSHOT/testing-1.0-20170509.055327-1-sources.jar
new file mode 100644
index 0000000..190f5e4
--- /dev/null
+++ b/samples-flatfoot/prebuilts0905/android/arch/persistence/room/testing/1.0-SNAPSHOT/testing-1.0-20170509.055327-1-sources.jar
Binary files differ
diff --git a/samples-flatfoot/prebuilts0905/android/arch/persistence/room/testing/1.0-SNAPSHOT/testing-1.0-20170509.055327-1-sources.jar.md5 b/samples-flatfoot/prebuilts0905/android/arch/persistence/room/testing/1.0-SNAPSHOT/testing-1.0-20170509.055327-1-sources.jar.md5
new file mode 100644
index 0000000..18f461c
--- /dev/null
+++ b/samples-flatfoot/prebuilts0905/android/arch/persistence/room/testing/1.0-SNAPSHOT/testing-1.0-20170509.055327-1-sources.jar.md5
@@ -0,0 +1 @@
+4ce813e7662bb18f1a1160549c1e8aea
\ No newline at end of file
diff --git a/samples-flatfoot/prebuilts0905/android/arch/persistence/room/testing/1.0-SNAPSHOT/testing-1.0-20170509.055327-1-sources.jar.sha1 b/samples-flatfoot/prebuilts0905/android/arch/persistence/room/testing/1.0-SNAPSHOT/testing-1.0-20170509.055327-1-sources.jar.sha1
new file mode 100644
index 0000000..dc67b23
--- /dev/null
+++ b/samples-flatfoot/prebuilts0905/android/arch/persistence/room/testing/1.0-SNAPSHOT/testing-1.0-20170509.055327-1-sources.jar.sha1
@@ -0,0 +1 @@
+be6096c6c0e24cdaece94a81f311d632eabf5481
\ No newline at end of file
diff --git a/samples-flatfoot/prebuilts0905/android/arch/persistence/room/testing/1.0-SNAPSHOT/testing-1.0-20170509.055327-1.aar b/samples-flatfoot/prebuilts0905/android/arch/persistence/room/testing/1.0-SNAPSHOT/testing-1.0-20170509.055327-1.aar
new file mode 100644
index 0000000..aea6991
--- /dev/null
+++ b/samples-flatfoot/prebuilts0905/android/arch/persistence/room/testing/1.0-SNAPSHOT/testing-1.0-20170509.055327-1.aar
Binary files differ
diff --git a/samples-flatfoot/prebuilts0905/android/arch/persistence/room/testing/1.0-SNAPSHOT/testing-1.0-20170509.055327-1.aar.md5 b/samples-flatfoot/prebuilts0905/android/arch/persistence/room/testing/1.0-SNAPSHOT/testing-1.0-20170509.055327-1.aar.md5
new file mode 100644
index 0000000..62ff241
--- /dev/null
+++ b/samples-flatfoot/prebuilts0905/android/arch/persistence/room/testing/1.0-SNAPSHOT/testing-1.0-20170509.055327-1.aar.md5
@@ -0,0 +1 @@
+cdcceb69a05e598ea45569fcd30dec7e
\ No newline at end of file
diff --git a/samples-flatfoot/prebuilts0905/android/arch/persistence/room/testing/1.0-SNAPSHOT/testing-1.0-20170509.055327-1.aar.sha1 b/samples-flatfoot/prebuilts0905/android/arch/persistence/room/testing/1.0-SNAPSHOT/testing-1.0-20170509.055327-1.aar.sha1
new file mode 100644
index 0000000..b4090a5
--- /dev/null
+++ b/samples-flatfoot/prebuilts0905/android/arch/persistence/room/testing/1.0-SNAPSHOT/testing-1.0-20170509.055327-1.aar.sha1
@@ -0,0 +1 @@
+9bc04de1a0494539696ad1a97d9b4eb3afe14f86
\ No newline at end of file
diff --git a/samples-flatfoot/prebuilts0905/android/arch/persistence/room/testing/1.0-SNAPSHOT/testing-1.0-20170509.055327-1.pom b/samples-flatfoot/prebuilts0905/android/arch/persistence/room/testing/1.0-SNAPSHOT/testing-1.0-20170509.055327-1.pom
new file mode 100644
index 0000000..e05307c
--- /dev/null
+++ b/samples-flatfoot/prebuilts0905/android/arch/persistence/room/testing/1.0-SNAPSHOT/testing-1.0-20170509.055327-1.pom
@@ -0,0 +1,59 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd" xmlns="http://maven.apache.org/POM/4.0.0"
+    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
+  <modelVersion>4.0.0</modelVersion>
+  <groupId>android.arch.persistence.room</groupId>
+  <artifactId>testing</artifactId>
+  <version>1.0-SNAPSHOT</version>
+  <packaging>aar</packaging>
+  <dependencies>
+    <dependency>
+      <groupId>android.arch.persistence.room</groupId>
+      <artifactId>common</artifactId>
+      <version>1.0-SNAPSHOT</version>
+      <scope>compile</scope>
+    </dependency>
+    <dependency>
+      <groupId>android.arch.persistence.room</groupId>
+      <artifactId>runtime</artifactId>
+      <version>1.0-SNAPSHOT</version>
+      <scope>compile</scope>
+    </dependency>
+    <dependency>
+      <groupId>android.arch.persistence.room</groupId>
+      <artifactId>support-db</artifactId>
+      <version>1.0-SNAPSHOT</version>
+      <scope>compile</scope>
+    </dependency>
+    <dependency>
+      <groupId>android.arch.persistence.room</groupId>
+      <artifactId>support-db-impl</artifactId>
+      <version>1.0-SNAPSHOT</version>
+      <scope>compile</scope>
+    </dependency>
+    <dependency>
+      <groupId>android.arch.persistence.room</groupId>
+      <artifactId>migration</artifactId>
+      <version>1.0-SNAPSHOT</version>
+      <scope>compile</scope>
+    </dependency>
+    <dependency>
+      <groupId>android.arch.core</groupId>
+      <artifactId>core</artifactId>
+      <version>1.0-SNAPSHOT</version>
+      <scope>compile</scope>
+    </dependency>
+    <dependency>
+      <groupId>com.android.support</groupId>
+      <artifactId>support-core-utils</artifactId>
+      <version>25.2.0</version>
+      <scope>compile</scope>
+    </dependency>
+    <dependency>
+      <groupId>junit</groupId>
+      <artifactId>junit</artifactId>
+      <version>4.12</version>
+      <scope>compile</scope>
+    </dependency>
+  </dependencies>
+</project>
diff --git a/samples-flatfoot/prebuilts0905/android/arch/persistence/room/testing/1.0-SNAPSHOT/testing-1.0-20170509.055327-1.pom.md5 b/samples-flatfoot/prebuilts0905/android/arch/persistence/room/testing/1.0-SNAPSHOT/testing-1.0-20170509.055327-1.pom.md5
new file mode 100644
index 0000000..95dd555
--- /dev/null
+++ b/samples-flatfoot/prebuilts0905/android/arch/persistence/room/testing/1.0-SNAPSHOT/testing-1.0-20170509.055327-1.pom.md5
@@ -0,0 +1 @@
+a8ff5e6331cbfbb65cfe54098c53fef7
\ No newline at end of file
diff --git a/samples-flatfoot/prebuilts0905/android/arch/persistence/room/testing/1.0-SNAPSHOT/testing-1.0-20170509.055327-1.pom.sha1 b/samples-flatfoot/prebuilts0905/android/arch/persistence/room/testing/1.0-SNAPSHOT/testing-1.0-20170509.055327-1.pom.sha1
new file mode 100644
index 0000000..972fbc3
--- /dev/null
+++ b/samples-flatfoot/prebuilts0905/android/arch/persistence/room/testing/1.0-SNAPSHOT/testing-1.0-20170509.055327-1.pom.sha1
@@ -0,0 +1 @@
+0d39779bea0506af5ffa78167035cd62ad72e8b7
\ No newline at end of file
diff --git a/samples-flatfoot/prebuilts0905/android/arch/persistence/room/testing/maven-metadata.xml b/samples-flatfoot/prebuilts0905/android/arch/persistence/room/testing/maven-metadata.xml
new file mode 100644
index 0000000..0784805
--- /dev/null
+++ b/samples-flatfoot/prebuilts0905/android/arch/persistence/room/testing/maven-metadata.xml
@@ -0,0 +1,11 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<metadata>
+  <groupId>android.arch.persistence.room</groupId>
+  <artifactId>testing</artifactId>
+  <versioning>
+    <versions>
+      <version>1.0-SNAPSHOT</version>
+    </versions>
+    <lastUpdated>20170509055327</lastUpdated>
+  </versioning>
+</metadata>
diff --git a/samples-flatfoot/prebuilts0905/android/arch/persistence/room/testing/maven-metadata.xml.md5 b/samples-flatfoot/prebuilts0905/android/arch/persistence/room/testing/maven-metadata.xml.md5
new file mode 100644
index 0000000..e6a97a9
--- /dev/null
+++ b/samples-flatfoot/prebuilts0905/android/arch/persistence/room/testing/maven-metadata.xml.md5
@@ -0,0 +1 @@
+2132c0282da897b43ed3b8e50548dcc2
\ No newline at end of file
diff --git a/samples-flatfoot/prebuilts0905/android/arch/persistence/room/testing/maven-metadata.xml.sha1 b/samples-flatfoot/prebuilts0905/android/arch/persistence/room/testing/maven-metadata.xml.sha1
new file mode 100644
index 0000000..c167f8a
--- /dev/null
+++ b/samples-flatfoot/prebuilts0905/android/arch/persistence/room/testing/maven-metadata.xml.sha1
@@ -0,0 +1 @@
+f8628435c4e8e8d8f8ae3bf0c4a1f16ebd40c80b
\ No newline at end of file
diff --git a/samples/Support7Demos/src/com/example/android/supportv7/media/OverlayDisplayWindow.java b/samples/Support7Demos/src/com/example/android/supportv7/media/OverlayDisplayWindow.java
index 6558f86..d6a82ef 100644
--- a/samples/Support7Demos/src/com/example/android/supportv7/media/OverlayDisplayWindow.java
+++ b/samples/Support7Demos/src/com/example/android/supportv7/media/OverlayDisplayWindow.java
@@ -126,8 +126,15 @@
 
                 Display display = mWindowManager.getDefaultDisplay();
 
-                WindowManager.LayoutParams params = new WindowManager.LayoutParams(
-                        WindowManager.LayoutParams.TYPE_SYSTEM_ALERT);
+                WindowManager.LayoutParams params;
+                if (Build.VERSION.SDK_INT >= 26) {
+                    // TYPE_SYSTEM_ALERT is deprecated in android O.
+                    params = new WindowManager.LayoutParams(
+                            WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY);
+                } else {
+                    params = new WindowManager.LayoutParams(
+                            WindowManager.LayoutParams.TYPE_SYSTEM_ALERT);
+                }
                 params.flags |= WindowManager.LayoutParams.FLAG_LAYOUT_IN_SCREEN
                         | WindowManager.LayoutParams.FLAG_LAYOUT_NO_LIMITS
                         | WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE
@@ -298,8 +305,14 @@
                     R.id.overlay_display_window_title);
             mNameTextView.setText(mName);
 
-            mWindowParams = new WindowManager.LayoutParams(
-                    WindowManager.LayoutParams.TYPE_SYSTEM_ALERT);
+            if (Build.VERSION.SDK_INT >= 26) {
+                // TYPE_SYSTEM_ALERT is deprecated in android O.
+                mWindowParams = new WindowManager.LayoutParams(
+                        WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY);
+            } else {
+                mWindowParams = new WindowManager.LayoutParams(
+                        WindowManager.LayoutParams.TYPE_SYSTEM_ALERT);
+            }
             mWindowParams.flags |= WindowManager.LayoutParams.FLAG_LAYOUT_IN_SCREEN
                     | WindowManager.LayoutParams.FLAG_LAYOUT_NO_LIMITS
                     | WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE
diff --git a/samples/Support7Demos/src/com/example/android/supportv7/media/SampleMediaRouterActivity.java b/samples/Support7Demos/src/com/example/android/supportv7/media/SampleMediaRouterActivity.java
index 846c5e0..c486a1a 100644
--- a/samples/Support7Demos/src/com/example/android/supportv7/media/SampleMediaRouterActivity.java
+++ b/samples/Support7Demos/src/com/example/android/supportv7/media/SampleMediaRouterActivity.java
@@ -29,6 +29,7 @@
 import android.os.Environment;
 import android.os.Handler;
 import android.os.SystemClock;
+import android.provider.Settings;
 import android.support.v4.app.FragmentManager;
 import android.support.v4.media.session.MediaSessionCompat;
 import android.support.v4.view.MenuItemCompat;
@@ -216,6 +217,13 @@
         // Be sure to call the super class.
         super.onCreate(savedInstanceState);
 
+        // Need overlay permission for emulating remote display.
+        if (Build.VERSION.SDK_INT >= 23 && !Settings.canDrawOverlays(this)) {
+            Intent intent = new Intent(Settings.ACTION_MANAGE_OVERLAY_PERMISSION,
+                    Uri.parse("package:" + getPackageName()));
+            startActivityForResult(intent, 0);
+        }
+
         // Get the media router service.
         mMediaRouter = MediaRouter.getInstance(this);
 
diff --git a/samples/SupportLeanbackDemos/AndroidManifest.xml b/samples/SupportLeanbackDemos/AndroidManifest.xml
index 93bddce..010c297 100644
--- a/samples/SupportLeanbackDemos/AndroidManifest.xml
+++ b/samples/SupportLeanbackDemos/AndroidManifest.xml
@@ -189,5 +189,8 @@
 
         <activity android:name=".VideoSupportActivity"
             android:exported="true" />
+
+        <activity android:name=".VideoActivityWithDetailedCard"
+                  android:exported="true" />
     </application>
 </manifest>
diff --git a/samples/SupportLeanbackDemos/res/drawable/google_map.jpg b/samples/SupportLeanbackDemos/res/drawable/google_map.jpg
new file mode 100644
index 0000000..447de09
--- /dev/null
+++ b/samples/SupportLeanbackDemos/res/drawable/google_map.jpg
Binary files differ
diff --git a/samples/SupportLeanbackDemos/res/layout/video_activity_detailed_card.xml b/samples/SupportLeanbackDemos/res/layout/video_activity_detailed_card.xml
new file mode 100644
index 0000000..ddacf0f
--- /dev/null
+++ b/samples/SupportLeanbackDemos/res/layout/video_activity_detailed_card.xml
@@ -0,0 +1,23 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  ~ Copyright (C) 2017 The Android Open Source Project
+  ~
+  ~ Licensed under the Apache License, Version 2.0 (the "License");
+  ~ you may not use this file except in compliance with the License.
+  ~ You may obtain a copy of the License at
+  ~
+  ~      http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing, software
+  ~ distributed under the License is distributed on an "AS IS" BASIS,
+  ~ WITHOUT 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="match_parent"
+                android:id="@+id/videoFragment">
+
+</RelativeLayout>
\ No newline at end of file
diff --git a/samples/SupportLeanbackDemos/res/values/strings.xml b/samples/SupportLeanbackDemos/res/values/strings.xml
index 6de7d7b..4e4a398 100644
--- a/samples/SupportLeanbackDemos/res/values/strings.xml
+++ b/samples/SupportLeanbackDemos/res/values/strings.xml
@@ -79,6 +79,8 @@
     <string name="onboarding">Onboarding</string>
     <string name="onboarding_description">Show onboarding activity.</string>
     <string name="onboarding_support">Onboarding(support version)</string>
+    <string name="video_play_with_detail_card">A video play activity with detail card</string>
+    <string name="video_play_with_detail_card_description">A video play activity with detail card</string>
 
     <!-- Strings related to guided sequence activity -->
     <string name="guidedstep_first_title">First</string>
diff --git a/samples/SupportLeanbackDemos/src/com/example/android/leanback/DetailsPresenterSelectionActivity.java b/samples/SupportLeanbackDemos/src/com/example/android/leanback/DetailsPresenterSelectionActivity.java
index 4f8546e..e6cf32f 100644
--- a/samples/SupportLeanbackDemos/src/com/example/android/leanback/DetailsPresenterSelectionActivity.java
+++ b/samples/SupportLeanbackDemos/src/com/example/android/leanback/DetailsPresenterSelectionActivity.java
@@ -69,7 +69,10 @@
                 .build());
     }
 
-    private static class SetupFragment extends GuidedStepFragment {
+    /**
+     * Fragment hosted in DetailsPresenterSelectionActivity.
+     */
+    public static class SetupFragment extends GuidedStepFragment {
 
         @Override
         public Guidance onCreateGuidance(Bundle savedInstanceState) {
diff --git a/samples/SupportLeanbackDemos/src/com/example/android/leanback/MainActivity.java b/samples/SupportLeanbackDemos/src/com/example/android/leanback/MainActivity.java
index 733697c..4293dbb 100644
--- a/samples/SupportLeanbackDemos/src/com/example/android/leanback/MainActivity.java
+++ b/samples/SupportLeanbackDemos/src/com/example/android/leanback/MainActivity.java
@@ -60,43 +60,45 @@
             addAction(actions, BrowseSupportActivity.class, R.string.browse_support,
                     R.string.browse_support_description);
             addAction(actions, SearchActivity.class, R.string.search, R.string.search_description);
-            addAction(actions, SearchSupportActivity.class, R.string.search_support, R.string.search_support_description);
+            addAction(actions, SearchSupportActivity.class, R.string.search_support,
+                    R.string.search_support_description);
 
             addAction(actions, DetailsActivity.class, R.string.details,
                     R.string.details_description);
-            actions.get(actions.size()-1).getIntent().putExtra(DetailsActivity.EXTRA_ITEM,
+            actions.get(actions.size() - 1).getIntent().putExtra(DetailsActivity.EXTRA_ITEM,
                     new PhotoItem("Hello world", R.drawable.gallery_photo_1));
             addAction(actions, DetailsSupportActivity.class, R.string.details_support,
                     R.string.details_support_description);
-            actions.get(actions.size()-1).getIntent().putExtra(DetailsSupportActivity.EXTRA_ITEM,
+            actions.get(actions.size() - 1).getIntent().putExtra(DetailsSupportActivity.EXTRA_ITEM,
                     new PhotoItem("Hello world", R.drawable.gallery_photo_1));
 
             addAction(actions, DetailsVideoActivity.class, R.string.details_video,
                     R.string.details_video_description);
-            actions.get(actions.size()-1).getIntent().putExtra(DetailsActivity.EXTRA_ITEM,
+            actions.get(actions.size() - 1).getIntent().putExtra(DetailsActivity.EXTRA_ITEM,
                     new PhotoItem("Hello world", R.drawable.gallery_photo_1));
             addAction(actions, DetailsVideoSupportActivity.class, R.string.details_video_support,
                     R.string.details_video_support_description);
-            actions.get(actions.size()-1).getIntent().putExtra(DetailsSupportActivity.EXTRA_ITEM,
+            actions.get(actions.size() - 1).getIntent().putExtra(DetailsSupportActivity.EXTRA_ITEM,
                     new PhotoItem("Hello world", R.drawable.gallery_photo_1));
 
             addAction(actions, DetailsCustomTitleActivity.class, R.string.details_custom_title,
                     R.string.details_custom_title_description);
-            actions.get(actions.size()-1).getIntent().putExtra(DetailsActivity.EXTRA_ITEM,
+            actions.get(actions.size() - 1).getIntent().putExtra(DetailsActivity.EXTRA_ITEM,
                     new PhotoItem("Hello world", R.drawable.gallery_photo_1));
             addAction(actions, DetailsCustomTitleSupportActivity.class,
                     R.string.details_custom_title_support,
                     R.string.details_custom_title_support_description);
-            actions.get(actions.size()-1).getIntent().putExtra(DetailsSupportActivity.EXTRA_ITEM,
+            actions.get(actions.size() - 1).getIntent().putExtra(DetailsSupportActivity.EXTRA_ITEM,
                     new PhotoItem("Hello world", R.drawable.gallery_photo_1));
 
             addAction(actions, SearchDetailsActivity.class, R.string.search_details,
                     R.string.search_details_description);
-            actions.get(actions.size()-1).getIntent().putExtra(SearchDetailsActivity.EXTRA_ITEM,
+            actions.get(actions.size() - 1).getIntent().putExtra(SearchDetailsActivity.EXTRA_ITEM,
                     new PhotoItem("Hello world", R.drawable.gallery_photo_1));
             addAction(actions, SearchDetailsSupportActivity.class, R.string.search_details_support,
                     R.string.search_details_support_description);
-            actions.get(actions.size()-1).getIntent().putExtra(SearchDetailsSupportActivity.EXTRA_ITEM,
+            actions.get(actions.size() - 1).getIntent().putExtra(
+                    SearchDetailsSupportActivity.EXTRA_ITEM,
                     new PhotoItem("Hello world", R.drawable.gallery_photo_1));
             addAction(actions, VerticalGridActivity.class, R.string.vgrid,
                     R.string.vgrid_description);
@@ -109,8 +111,8 @@
             addAction(actions, GuidedStepHalfScreenActivity.class, R.string.guidedstephalfscreen,
                     R.string.guidedstep_description);
             addAction(actions, GuidedStepSupportHalfScreenActivity.class,
-                R.string.guidedstepsupporthalfscreen,
-                R.string.guidedstep_description);
+                    R.string.guidedstepsupporthalfscreen,
+                    R.string.guidedstep_description);
             addAction(actions, BrowseErrorActivity.class, R.string.browseerror,
                     R.string.browseerror_description);
             addAction(actions, BrowseErrorSupportActivity.class, R.string.browseerror_support,
@@ -141,6 +143,9 @@
             addAction(actions, OnboardingSupportActivity.class,
                     R.string.onboarding_support,
                     R.string.onboarding_description);
+            addAction(actions, VideoActivityWithDetailedCard.class,
+                    R.string.video_play_with_detail_card,
+                    R.string.video_play_with_detail_card_description);
         }
 
         private void addAction(List<GuidedAction> actions, Class cls, int titleRes, int descRes) {
diff --git a/samples/SupportLeanbackDemos/src/com/example/android/leanback/VideoActivityWithDetailedCard.java b/samples/SupportLeanbackDemos/src/com/example/android/leanback/VideoActivityWithDetailedCard.java
new file mode 100644
index 0000000..5acecd7
--- /dev/null
+++ b/samples/SupportLeanbackDemos/src/com/example/android/leanback/VideoActivityWithDetailedCard.java
@@ -0,0 +1,71 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES 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.leanback;
+
+import android.app.Activity;
+import android.app.FragmentTransaction;
+import android.content.Context;
+import android.content.Intent;
+import android.content.pm.PackageManager;
+import android.os.Bundle;
+import android.support.v4.os.BuildCompat;
+
+/**
+ * Activity that hosts VideoConsumptionExampleFragment.
+ *
+ * The main purpose to add this activity is to observe the bug b/28003943
+ */
+public class VideoActivityWithDetailedCard extends Activity {
+
+    public static final String TAG = "VideoExampleActivity";
+
+    @Override
+    public void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+        setContentView(R.layout.video_activity_detailed_card);
+
+        if (savedInstanceState == null) {
+            FragmentTransaction ft = getFragmentManager().beginTransaction();
+            ft.add(R.id.videoFragment, new VideoConsumptionWithDetailCardFragment(),
+                    VideoConsumptionWithDetailCardFragment.TAG);
+            ft.commit();
+        }
+    }
+
+    @Override
+    protected void onNewIntent(Intent intent) {
+        super.onNewIntent(intent);
+        // This part is necessary to ensure that getIntent returns the latest intent when
+        // VideoExampleActivity is started. By default, getIntent() returns the initial intent
+        // that was set from another activity that started VideoExampleActivity. However, we need
+        // to update this intent when for example, user clicks on another video when the currently
+        // playing video is in PIP mode, and a new video needs to be started.
+        setIntent(intent);
+    }
+
+    /**
+     * Helper function to determine if picture in picture mode is supported or not
+     * @param context current context
+     * @return if Picture in Picture mode is supported or not
+     */
+    public static boolean supportsPictureInPicture(Context context) {
+        return BuildCompat.isAtLeastN()
+                && context.getPackageManager().hasSystemFeature(
+                        PackageManager.FEATURE_PICTURE_IN_PICTURE);
+    }
+}
+
diff --git a/samples/SupportLeanbackDemos/src/com/example/android/leanback/VideoConsumptionWithDetailCardFragment.java b/samples/SupportLeanbackDemos/src/com/example/android/leanback/VideoConsumptionWithDetailCardFragment.java
new file mode 100644
index 0000000..dd40c8f
--- /dev/null
+++ b/samples/SupportLeanbackDemos/src/com/example/android/leanback/VideoConsumptionWithDetailCardFragment.java
@@ -0,0 +1,93 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES 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.leanback;
+
+import android.net.Uri;
+import android.os.Bundle;
+import android.support.v17.leanback.app.VideoFragment;
+import android.support.v17.leanback.app.VideoFragmentGlueHost;
+import android.support.v17.leanback.media.MediaPlayerAdapter;
+import android.support.v17.leanback.media.PlaybackBannerControlGlue;
+import android.support.v17.leanback.media.PlaybackGlue;
+
+/**
+ * Fragment used as Control Glue's host
+ */
+public class VideoConsumptionWithDetailCardFragment extends VideoFragment {
+
+    public static final String TAG = "VideoConsumptionWithDetailCardFragment";
+    // A valid video URL to play video. So the progress bar can be seen to reproduce the bug.
+    private static final String VIDEO_URL =
+            "https://storage.googleapis.com/android-tv/Sample videos/"
+                    + "April Fool's 2013/Explore Treasure Mode with Google Maps.mp4";
+    public static final String TITLE = "Diving with Sharks";
+    public static final String SUBTITLE = "A Googler";
+
+    private PlaybackBannerControlGlue<MediaPlayerAdapter> mMediaPlayerGlue;
+    final VideoFragmentGlueHost mHost = new VideoFragmentGlueHost(this);
+
+    /**
+     * helper function for playBackGlue to add/ remove callbacks
+     *
+     * @param glue The playback glue attached to this fragment
+     */
+    private static void playWhenReady(PlaybackGlue glue) {
+        if (glue.isPrepared()) {
+            glue.play();
+        } else {
+            glue.addPlayerCallback(new PlaybackGlue.PlayerCallback() {
+                @Override
+                public void onPreparedStateChanged(PlaybackGlue glue) {
+                    if (glue.isPrepared()) {
+                        glue.removePlayerCallback(this);
+                        glue.play();
+                    }
+                }
+            });
+        }
+    }
+
+    @Override
+    public void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+
+        int[] defuatSpeed = new int[]{1};
+        mMediaPlayerGlue = new PlaybackBannerControlGlue<>(getActivity(), defuatSpeed,
+                new MediaPlayerAdapter(getActivity()));
+        // attach player glue to current host
+        mMediaPlayerGlue.setHost(mHost);
+
+        // add image resource to the PlaybackControlGlue
+        mMediaPlayerGlue.setArt(getActivity().getDrawable(R.drawable.google_map));
+
+        // meta information for video player
+        mMediaPlayerGlue.setTitle(TITLE);
+        mMediaPlayerGlue.setSubtitle(SUBTITLE);
+        mMediaPlayerGlue.getPlayerAdapter().setDataSource(Uri.parse(VIDEO_URL));
+        playWhenReady(mMediaPlayerGlue);
+        setBackgroundType(BG_LIGHT);
+    }
+
+    @Override
+    public void onPause() {
+        if (mMediaPlayerGlue != null) {
+            mMediaPlayerGlue.pause();
+        }
+        super.onPause();
+    }
+}
+
diff --git a/samples/SupportLeanbackShowcase/app/src/main/res/drawable-xhdpi/google_map.jpg b/samples/SupportLeanbackShowcase/app/src/main/res/drawable-xhdpi/google_map.jpg
new file mode 100644
index 0000000..447de09
--- /dev/null
+++ b/samples/SupportLeanbackShowcase/app/src/main/res/drawable-xhdpi/google_map.jpg
Binary files differ
diff --git a/samples/SupportWearDemos/AndroidManifest.xml b/samples/SupportWearDemos/AndroidManifest.xml
index eb10f0a..4e4fb60 100644
--- a/samples/SupportWearDemos/AndroidManifest.xml
+++ b/samples/SupportWearDemos/AndroidManifest.xml
@@ -24,6 +24,8 @@
         </activity>
         <activity android:name="com.example.android.support.wear.app.WearableSwitchDemo">
         </activity>
+        <activity android:name="com.example.android.support.wear.app.CircularProgressLayoutDemo">
+        </activity>
         <activity android:name="com.example.android.support.wear.app.MainDemoActivity">
             <intent-filter>
                 <action android:name="android.intent.action.MAIN" />
diff --git a/samples/SupportWearDemos/res/layout/cpl_demo.xml b/samples/SupportWearDemos/res/layout/cpl_demo.xml
new file mode 100644
index 0000000..cf29663
--- /dev/null
+++ b/samples/SupportWearDemos/res/layout/cpl_demo.xml
@@ -0,0 +1,42 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  ~ Copyright (C) 2017 The Android Open Source Project
+  ~
+  ~ Licensed under the Apache License, Version 2.0 (the "License");
+  ~ you may not use this file except in compliance with the License.
+  ~ You may obtain a copy of the License at
+  ~
+  ~      http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing, software
+  ~ distributed under the License is distributed on an "AS IS" BASIS,
+  ~ WITHOUT 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:app="http://schemas.android.com/apk/res-auto"
+             android:layout_width="match_parent"
+             android:layout_height="match_parent"
+             android:orientation="vertical">
+
+    <android.support.wear.widget.CircularProgressLayout
+        android:id="@+id/circularProgressLayout_layout"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:layout_gravity="center"
+        android:padding="10dp"
+        app:backgroundColor="@color/cpl_light_yellow"
+        app:colorSchemeColors="@color/cpl_light_blue"
+        app:strokeWidth="10dp">
+        <TextView
+            android:id="@+id/circularProgressLayout_child"
+            android:layout_width="60dp"
+            android:layout_height="60dp"
+            android:layout_gravity="center"
+            android:gravity="center"
+            android:textColor="@color/cpl_black"
+            android:text="@string/cpl_click_me"/>
+    </android.support.wear.widget.CircularProgressLayout>
+</FrameLayout>
\ No newline at end of file
diff --git a/samples/SupportWearDemos/res/values/colors.xml b/samples/SupportWearDemos/res/values/colors.xml
new file mode 100644
index 0000000..95c0119
--- /dev/null
+++ b/samples/SupportWearDemos/res/values/colors.xml
@@ -0,0 +1,24 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  ~ Copyright (C) 2017 The Android Open Source Project
+  ~
+  ~ Licensed under the Apache License, Version 2.0 (the "License");
+  ~ you may not use this file except in compliance with the License.
+  ~ You may obtain a copy of the License at
+  ~
+  ~      http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing, software
+  ~ distributed under the License is distributed on an "AS IS" BASIS,
+  ~ WITHOUT 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>
+    <color name="cpl_light_yellow">#fff176</color>
+    <color name="cpl_light_red">#ef5350</color>
+    <color name="cpl_light_green">#66bb6a</color>
+    <color name="cpl_light_blue">#4fc3f7</color>
+    <color name="cpl_black">#000000</color>
+</resources>
\ No newline at end of file
diff --git a/samples/SupportWearDemos/res/values/strings.xml b/samples/SupportWearDemos/res/values/strings.xml
new file mode 100644
index 0000000..7f92211
--- /dev/null
+++ b/samples/SupportWearDemos/res/values/strings.xml
@@ -0,0 +1,22 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  ~ Copyright (C) 2017 The Android Open Source Project
+  ~
+  ~ Licensed under the Apache License, Version 2.0 (the "License");
+  ~ you may not use this file except in compliance with the License.
+  ~ You may obtain a copy of the License at
+  ~
+  ~      http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing, software
+  ~ distributed under the License is distributed on an "AS IS" BASIS,
+  ~ WITHOUT 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="cpl_click_me">Click me!</string>
+    <string name="cpl_clicked">Clicked!</string>
+    <string name="cpl_finished">Finished!</string>
+</resources>
\ No newline at end of file
diff --git a/samples/SupportWearDemos/src/com/example/android/support/wear/app/CircularProgressLayoutDemo.java b/samples/SupportWearDemos/src/com/example/android/support/wear/app/CircularProgressLayoutDemo.java
new file mode 100644
index 0000000..6bd4438
--- /dev/null
+++ b/samples/SupportWearDemos/src/com/example/android/support/wear/app/CircularProgressLayoutDemo.java
@@ -0,0 +1,77 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES 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.support.wear.app;
+
+import android.app.Activity;
+import android.os.Build;
+import android.os.Bundle;
+import android.support.annotation.Nullable;
+import android.support.annotation.RequiresApi;
+import android.support.v4.content.ContextCompat;
+import android.support.wear.widget.CircularProgressLayout;
+import android.view.View;
+import android.widget.TextView;
+
+import com.example.android.support.wear.R;
+
+import java.util.concurrent.TimeUnit;
+
+/**
+ * Main activity for the CircularProgressLayout demo.
+ */
+@RequiresApi(Build.VERSION_CODES.LOLLIPOP)
+public class CircularProgressLayoutDemo extends Activity implements
+        CircularProgressLayout.OnTimerFinishedListener, View.OnClickListener {
+
+    private static final long TOTAL_TIME = TimeUnit.SECONDS.toMillis(10);
+
+    CircularProgressLayout mCircularProgressLayout;
+    TextView mChildView;
+
+    @Override
+    protected void onCreate(@Nullable Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+        setContentView(R.layout.cpl_demo);
+        mCircularProgressLayout = findViewById(R.id.circularProgressLayout_layout);
+        mChildView = findViewById(R.id.circularProgressLayout_child);
+
+        mCircularProgressLayout.setOnClickListener(this);
+        mCircularProgressLayout.setOnTimerFinishedListener(this);
+
+        mCircularProgressLayout.setTotalTime(TOTAL_TIME);
+        mCircularProgressLayout.startTimer();
+    }
+
+    @Override
+    public void onTimerFinished(CircularProgressLayout layout) {
+        if (layout == mCircularProgressLayout) {
+            mChildView.setText(getString(R.string.cpl_finished));
+            mCircularProgressLayout.setBackgroundColor(
+                    ContextCompat.getColor(this, R.color.cpl_light_green));
+        }
+    }
+
+    @Override
+    public void onClick(View view) {
+        if (view == mCircularProgressLayout && mCircularProgressLayout.isTimerRunning()) {
+            mCircularProgressLayout.stopTimer();
+            mChildView.setText(getString(R.string.cpl_clicked));
+            mCircularProgressLayout.setBackgroundColor(
+                    ContextCompat.getColor(this, R.color.cpl_light_red));
+        }
+    }
+}
diff --git a/samples/SupportWearDemos/src/com/example/android/support/wear/app/MainDemoActivity.java b/samples/SupportWearDemos/src/com/example/android/support/wear/app/MainDemoActivity.java
index c9512a2..313457b 100644
--- a/samples/SupportWearDemos/src/com/example/android/support/wear/app/MainDemoActivity.java
+++ b/samples/SupportWearDemos/src/com/example/android/support/wear/app/MainDemoActivity.java
@@ -54,12 +54,15 @@
                 this, SimpleWearableRecyclerViewDemo.class));
         contentMap.put("Wearable Switch", new Intent(
                 this, WearableSwitchDemo.class));
+        contentMap.put("Circular Progress Layout", new Intent(
+                this, CircularProgressLayoutDemo.class));
 
         return contentMap;
     }
 
     private class ViewHolder extends RecyclerView.ViewHolder {
         Button mView;
+
         ViewHolder(Button itemView) {
             super(itemView);
             mView = itemView;
diff --git a/settings.gradle b/settings.gradle
index dd3db92..ffc34ee 100644
--- a/settings.gradle
+++ b/settings.gradle
@@ -94,9 +94,6 @@
 include ':support-tv-provider'
 project(':support-tv-provider').projectDir = new File(rootDir, 'tv-provider')
 
-include ':support-instantvideo'
-project(':support-instantvideo').projectDir = new File(rootDir, 'instantvideo')
-
 include ':support-emoji'
 project(':support-emoji').projectDir = new File(rootDir, 'emoji/core')
 
diff --git a/transition/lint-baseline.xml b/transition/lint-baseline.xml
index 2a79d28..b373b5e 100644
--- a/transition/lint-baseline.xml
+++ b/transition/lint-baseline.xml
@@ -1,191 +1,15 @@
 <?xml version="1.0" encoding="UTF-8"?>
-<issues format="4" by="lint 2.4.0-alpha6">
-
-    <issue
-        id="MissingSuperCall"
-        message="Overriding method should call `super.onAnimationEnd`"
-        errorLine1="                public void onAnimationEnd(Animation animation) {"
-        errorLine2="                            ~~~~~~~~~~~~~~">
-        <location
-            file="java/android/support/v4/app/FragmentManager.java"
-            line="1604"
-            column="29"/>
-    </issue>
-
-    <issue
-        id="MissingSuperCall"
-        message="Overriding method should call `super.onAnimationEnd`"
-        errorLine1="        public void onAnimationEnd(Animation animation) {"
-        errorLine2="                    ~~~~~~~~~~~~~~">
-        <location
-            file="java/android/support/v4/app/FragmentManager.java"
-            line="3935"
-            column="21"/>
-    </issue>
-
-    <issue
-        id="Range"
-        message="Value must be ≥ 0 (was -1)"
-        errorLine1="                childHeightSpec = MeasureSpec.makeMeasureSpec(maxLayoutHeight, MeasureSpec.AT_MOST);"
-        errorLine2="                                                              ~~~~~~~~~~~~~~~">
-        <location
-            file="java/android/support/v4/widget/SlidingPaneLayout.java"
-            line="536"
-            column="63"/>
-    </issue>
-
-    <issue
-        id="Range"
-        message="Value must be ≥ 0 (was -1)"
-        errorLine1="                childHeightSpec = MeasureSpec.makeMeasureSpec(maxLayoutHeight, MeasureSpec.EXACTLY);"
-        errorLine2="                                                              ~~~~~~~~~~~~~~~">
-        <location
-            file="java/android/support/v4/widget/SlidingPaneLayout.java"
-            line="538"
-            column="63"/>
-    </issue>
-
-    <issue
-        id="Range"
-        message="Value must be ≥ 0 (was -1)"
-        errorLine1="                                childHeightSpec = MeasureSpec.makeMeasureSpec(maxLayoutHeight,"
-        errorLine2="                                                                              ~~~~~~~~~~~~~~~">
-        <location
-            file="java/android/support/v4/widget/SlidingPaneLayout.java"
-            line="586"
-            column="79"/>
-    </issue>
-
-    <issue
-        id="Range"
-        message="Value must be ≥ 0 (was -1)"
-        errorLine1="                                childHeightSpec = MeasureSpec.makeMeasureSpec(maxLayoutHeight,"
-        errorLine2="                                                                              ~~~~~~~~~~~~~~~">
-        <location
-            file="java/android/support/v4/widget/SlidingPaneLayout.java"
-            line="589"
-            column="79"/>
-    </issue>
-
-    <issue
-        id="Range"
-        message="Value must be ≥ 0 (was -1)"
-        errorLine1="                            childHeightSpec = MeasureSpec.makeMeasureSpec(maxLayoutHeight,"
-        errorLine2="                                                                          ~~~~~~~~~~~~~~~">
-        <location
-            file="java/android/support/v4/widget/SlidingPaneLayout.java"
-            line="608"
-            column="75"/>
-    </issue>
-
-    <issue
-        id="Range"
-        message="Value must be ≥ 0 (was -1)"
-        errorLine1="                            childHeightSpec = MeasureSpec.makeMeasureSpec(maxLayoutHeight,"
-        errorLine2="                                                                          ~~~~~~~~~~~~~~~">
-        <location
-            file="java/android/support/v4/widget/SlidingPaneLayout.java"
-            line="611"
-            column="75"/>
-    </issue>
-
-    <issue
-        id="LongLogTag"
-        message="The logging tag can be at most 23 characters, was 25 (FragmentStatePagerAdapter)"
-        errorLine1="        if (DEBUG) Log.v(TAG, &quot;Adding item #&quot; + position + &quot;: f=&quot; + fragment);"
-        errorLine2="                         ~~~">
-        <location
-            file="java/android/support/v4/app/FragmentStatePagerAdapter.java"
-            line="110"
-            column="26"/>
-    </issue>
-
-    <issue
-        id="LongLogTag"
-        message="The logging tag can be at most 23 characters, was 25 (FragmentStatePagerAdapter)"
-        errorLine1="        if (DEBUG) Log.v(TAG, &quot;Removing item #&quot; + position + &quot;: f=&quot; + object"
-        errorLine2="                         ~~~">
-        <location
-            file="java/android/support/v4/app/FragmentStatePagerAdapter.java"
-            line="135"
-            column="26"/>
-    </issue>
-
-    <issue
-        id="LongLogTag"
-        message="The logging tag can be at most 23 characters, was 25 (FragmentStatePagerAdapter)"
-        errorLine1="                        Log.w(TAG, &quot;Bad fragment at key &quot; + key);"
-        errorLine2="                              ~~~">
-        <location
-            file="java/android/support/v4/app/FragmentStatePagerAdapter.java"
-            line="224"
-            column="31"/>
-    </issue>
-
-    <issue
-        id="LongLogTag"
-        message="The logging tag can be at most 23 characters, was 28 (RoundedBitmapDrawableFactory)"
-        errorLine1="            Log.w(TAG, &quot;RoundedBitmapDrawable cannot decode &quot; + filepath);"
-        errorLine2="                  ~~~">
-        <location
-            file="java/android/support/v4/graphics/drawable/RoundedBitmapDrawableFactory.java"
-            line="80"
-            column="19"/>
-    </issue>
-
-    <issue
-        id="LongLogTag"
-        message="The logging tag can be at most 23 characters, was 28 (RoundedBitmapDrawableFactory)"
-        errorLine1="            Log.w(TAG, &quot;RoundedBitmapDrawable cannot decode &quot; + is);"
-        errorLine2="                  ~~~">
-        <location
-            file="java/android/support/v4/graphics/drawable/RoundedBitmapDrawableFactory.java"
-            line="93"
-            column="19"/>
-    </issue>
-
-    <issue
-        id="LongLogTag"
-        message="The logging tag can be at most 23 characters, was 24 (WakefulBroadcastReceiver)"
-        errorLine1="            Log.w(&quot;WakefulBroadcastReceiver&quot;, &quot;No active wake lock id #&quot; + id);"
-        errorLine2="                  ~~~~~~~~~~~~~~~~~~~~~~~~~~">
-        <location
-            file="java/android/support/v4/content/WakefulBroadcastReceiver.java"
-            line="141"
-            column="19"/>
-    </issue>
+<issues format="4" by="lint 3.0.0-alpha5">
 
     <issue
         id="WrongConstant"
-        message="Must be one of: Gravity.LEFT, Gravity.RIGHT, GravityCompat.START, GravityCompat.END"
-        errorLine1="        setDrawerLockMode(lockMode, gravity);"
-        errorLine2="                                    ~~~~~~~">
+        message="Must be one or more of: Gravity.LEFT, Gravity.RIGHT, GravityCompat.START, GravityCompat.END"
+        errorLine1="                            + gravityToString(childGravity) + &quot; but this &quot; + TAG + &quot; already has a &quot;"
+        errorLine2="                                              ~~~~~~~~~~~~">
         <location
             file="java/android/support/v4/widget/DrawerLayout.java"
-            line="680"
-            column="37"/>
-    </issue>
-
-    <issue
-        id="WrongConstant"
-        message="Must be one of: Gravity.LEFT, Gravity.RIGHT, GravityCompat.START, GravityCompat.END"
-        errorLine1="        return getDrawerLockMode(drawerGravity);"
-        errorLine2="                                 ~~~~~~~~~~~~~">
-        <location
-            file="java/android/support/v4/widget/DrawerLayout.java"
-            line="753"
-            column="34"/>
-    </issue>
-
-    <issue
-        id="WrongConstant"
-        message="Flag not allowed here"
-        errorLine1="                        getDrawerViewAbsoluteGravity(child) &amp; Gravity.HORIZONTAL_GRAVITY_MASK;"
-        errorLine2="                        ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
-        <location
-            file="java/android/support/v4/widget/DrawerLayout.java"
-            line="1086"
-            column="25"/>
+            line="1075"
+            column="47"/>
     </issue>
 
 </issues>
diff --git a/tv-provider/lint-baseline.xml b/tv-provider/lint-baseline.xml
index 95d2eae..8126671 100644
--- a/tv-provider/lint-baseline.xml
+++ b/tv-provider/lint-baseline.xml
@@ -1,5 +1,5 @@
 <?xml version="1.0" encoding="UTF-8"?>
-<issues format="4" by="lint 2.4.0-alpha6">
+<issues format="4" by="lint 3.0.0-alpha5">
 
     <issue
         id="WrongConstant"
diff --git a/v13/lint-baseline.xml b/v13/lint-baseline.xml
index 2a79d28..b373b5e 100644
--- a/v13/lint-baseline.xml
+++ b/v13/lint-baseline.xml
@@ -1,191 +1,15 @@
 <?xml version="1.0" encoding="UTF-8"?>
-<issues format="4" by="lint 2.4.0-alpha6">
-
-    <issue
-        id="MissingSuperCall"
-        message="Overriding method should call `super.onAnimationEnd`"
-        errorLine1="                public void onAnimationEnd(Animation animation) {"
-        errorLine2="                            ~~~~~~~~~~~~~~">
-        <location
-            file="java/android/support/v4/app/FragmentManager.java"
-            line="1604"
-            column="29"/>
-    </issue>
-
-    <issue
-        id="MissingSuperCall"
-        message="Overriding method should call `super.onAnimationEnd`"
-        errorLine1="        public void onAnimationEnd(Animation animation) {"
-        errorLine2="                    ~~~~~~~~~~~~~~">
-        <location
-            file="java/android/support/v4/app/FragmentManager.java"
-            line="3935"
-            column="21"/>
-    </issue>
-
-    <issue
-        id="Range"
-        message="Value must be ≥ 0 (was -1)"
-        errorLine1="                childHeightSpec = MeasureSpec.makeMeasureSpec(maxLayoutHeight, MeasureSpec.AT_MOST);"
-        errorLine2="                                                              ~~~~~~~~~~~~~~~">
-        <location
-            file="java/android/support/v4/widget/SlidingPaneLayout.java"
-            line="536"
-            column="63"/>
-    </issue>
-
-    <issue
-        id="Range"
-        message="Value must be ≥ 0 (was -1)"
-        errorLine1="                childHeightSpec = MeasureSpec.makeMeasureSpec(maxLayoutHeight, MeasureSpec.EXACTLY);"
-        errorLine2="                                                              ~~~~~~~~~~~~~~~">
-        <location
-            file="java/android/support/v4/widget/SlidingPaneLayout.java"
-            line="538"
-            column="63"/>
-    </issue>
-
-    <issue
-        id="Range"
-        message="Value must be ≥ 0 (was -1)"
-        errorLine1="                                childHeightSpec = MeasureSpec.makeMeasureSpec(maxLayoutHeight,"
-        errorLine2="                                                                              ~~~~~~~~~~~~~~~">
-        <location
-            file="java/android/support/v4/widget/SlidingPaneLayout.java"
-            line="586"
-            column="79"/>
-    </issue>
-
-    <issue
-        id="Range"
-        message="Value must be ≥ 0 (was -1)"
-        errorLine1="                                childHeightSpec = MeasureSpec.makeMeasureSpec(maxLayoutHeight,"
-        errorLine2="                                                                              ~~~~~~~~~~~~~~~">
-        <location
-            file="java/android/support/v4/widget/SlidingPaneLayout.java"
-            line="589"
-            column="79"/>
-    </issue>
-
-    <issue
-        id="Range"
-        message="Value must be ≥ 0 (was -1)"
-        errorLine1="                            childHeightSpec = MeasureSpec.makeMeasureSpec(maxLayoutHeight,"
-        errorLine2="                                                                          ~~~~~~~~~~~~~~~">
-        <location
-            file="java/android/support/v4/widget/SlidingPaneLayout.java"
-            line="608"
-            column="75"/>
-    </issue>
-
-    <issue
-        id="Range"
-        message="Value must be ≥ 0 (was -1)"
-        errorLine1="                            childHeightSpec = MeasureSpec.makeMeasureSpec(maxLayoutHeight,"
-        errorLine2="                                                                          ~~~~~~~~~~~~~~~">
-        <location
-            file="java/android/support/v4/widget/SlidingPaneLayout.java"
-            line="611"
-            column="75"/>
-    </issue>
-
-    <issue
-        id="LongLogTag"
-        message="The logging tag can be at most 23 characters, was 25 (FragmentStatePagerAdapter)"
-        errorLine1="        if (DEBUG) Log.v(TAG, &quot;Adding item #&quot; + position + &quot;: f=&quot; + fragment);"
-        errorLine2="                         ~~~">
-        <location
-            file="java/android/support/v4/app/FragmentStatePagerAdapter.java"
-            line="110"
-            column="26"/>
-    </issue>
-
-    <issue
-        id="LongLogTag"
-        message="The logging tag can be at most 23 characters, was 25 (FragmentStatePagerAdapter)"
-        errorLine1="        if (DEBUG) Log.v(TAG, &quot;Removing item #&quot; + position + &quot;: f=&quot; + object"
-        errorLine2="                         ~~~">
-        <location
-            file="java/android/support/v4/app/FragmentStatePagerAdapter.java"
-            line="135"
-            column="26"/>
-    </issue>
-
-    <issue
-        id="LongLogTag"
-        message="The logging tag can be at most 23 characters, was 25 (FragmentStatePagerAdapter)"
-        errorLine1="                        Log.w(TAG, &quot;Bad fragment at key &quot; + key);"
-        errorLine2="                              ~~~">
-        <location
-            file="java/android/support/v4/app/FragmentStatePagerAdapter.java"
-            line="224"
-            column="31"/>
-    </issue>
-
-    <issue
-        id="LongLogTag"
-        message="The logging tag can be at most 23 characters, was 28 (RoundedBitmapDrawableFactory)"
-        errorLine1="            Log.w(TAG, &quot;RoundedBitmapDrawable cannot decode &quot; + filepath);"
-        errorLine2="                  ~~~">
-        <location
-            file="java/android/support/v4/graphics/drawable/RoundedBitmapDrawableFactory.java"
-            line="80"
-            column="19"/>
-    </issue>
-
-    <issue
-        id="LongLogTag"
-        message="The logging tag can be at most 23 characters, was 28 (RoundedBitmapDrawableFactory)"
-        errorLine1="            Log.w(TAG, &quot;RoundedBitmapDrawable cannot decode &quot; + is);"
-        errorLine2="                  ~~~">
-        <location
-            file="java/android/support/v4/graphics/drawable/RoundedBitmapDrawableFactory.java"
-            line="93"
-            column="19"/>
-    </issue>
-
-    <issue
-        id="LongLogTag"
-        message="The logging tag can be at most 23 characters, was 24 (WakefulBroadcastReceiver)"
-        errorLine1="            Log.w(&quot;WakefulBroadcastReceiver&quot;, &quot;No active wake lock id #&quot; + id);"
-        errorLine2="                  ~~~~~~~~~~~~~~~~~~~~~~~~~~">
-        <location
-            file="java/android/support/v4/content/WakefulBroadcastReceiver.java"
-            line="141"
-            column="19"/>
-    </issue>
+<issues format="4" by="lint 3.0.0-alpha5">
 
     <issue
         id="WrongConstant"
-        message="Must be one of: Gravity.LEFT, Gravity.RIGHT, GravityCompat.START, GravityCompat.END"
-        errorLine1="        setDrawerLockMode(lockMode, gravity);"
-        errorLine2="                                    ~~~~~~~">
+        message="Must be one or more of: Gravity.LEFT, Gravity.RIGHT, GravityCompat.START, GravityCompat.END"
+        errorLine1="                            + gravityToString(childGravity) + &quot; but this &quot; + TAG + &quot; already has a &quot;"
+        errorLine2="                                              ~~~~~~~~~~~~">
         <location
             file="java/android/support/v4/widget/DrawerLayout.java"
-            line="680"
-            column="37"/>
-    </issue>
-
-    <issue
-        id="WrongConstant"
-        message="Must be one of: Gravity.LEFT, Gravity.RIGHT, GravityCompat.START, GravityCompat.END"
-        errorLine1="        return getDrawerLockMode(drawerGravity);"
-        errorLine2="                                 ~~~~~~~~~~~~~">
-        <location
-            file="java/android/support/v4/widget/DrawerLayout.java"
-            line="753"
-            column="34"/>
-    </issue>
-
-    <issue
-        id="WrongConstant"
-        message="Flag not allowed here"
-        errorLine1="                        getDrawerViewAbsoluteGravity(child) &amp; Gravity.HORIZONTAL_GRAVITY_MASK;"
-        errorLine2="                        ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
-        <location
-            file="java/android/support/v4/widget/DrawerLayout.java"
-            line="1086"
-            column="25"/>
+            line="1075"
+            column="47"/>
     </issue>
 
 </issues>
diff --git a/v14/preference/lint-baseline.xml b/v14/preference/lint-baseline.xml
index 65f542a..ee21bc3 100644
--- a/v14/preference/lint-baseline.xml
+++ b/v14/preference/lint-baseline.xml
@@ -1,5 +1,5 @@
 <?xml version="1.0" encoding="UTF-8"?>
-<issues format="4" by="lint 2.4.0-alpha6">
+<issues format="4" by="lint 3.0.0-alpha5">
 
     <issue
         id="MissingPermission"
@@ -14,28 +14,6 @@
 
     <issue
         id="MissingSuperCall"
-        message="Overriding method should call `super.onAnimationEnd`"
-        errorLine1="                public void onAnimationEnd(Animation animation) {"
-        errorLine2="                            ~~~~~~~~~~~~~~">
-        <location
-            file="java/android/support/v4/app/FragmentManager.java"
-            line="1604"
-            column="29"/>
-    </issue>
-
-    <issue
-        id="MissingSuperCall"
-        message="Overriding method should call `super.onAnimationEnd`"
-        errorLine1="        public void onAnimationEnd(Animation animation) {"
-        errorLine2="                    ~~~~~~~~~~~~~~">
-        <location
-            file="java/android/support/v4/app/FragmentManager.java"
-            line="3935"
-            column="21"/>
-    </issue>
-
-    <issue
-        id="MissingSuperCall"
         message="Overriding method should call `super.draw`"
         errorLine1="    public void draw(Canvas canvas) {"
         errorLine2="                ~~~~">
@@ -94,17 +72,6 @@
     </issue>
 
     <issue
-        id="ResourceType"
-        message="Expected resource of type xml"
-        errorLine1="            final XmlPullParser parser = res.getXml(resId);"
-        errorLine2="                                                    ~~~~~">
-        <location
-            file="src/android/support/graphics/drawable/VectorDrawableCompat.java"
-            line="557"
-            column="53"/>
-    </issue>
-
-    <issue
         id="Range"
         message="Value must be ≥ 0 (was -2147483648)"
         errorLine1="                                MeasureSpec.makeMeasureSpec(largestChildHeight,"
@@ -127,72 +94,6 @@
     </issue>
 
     <issue
-        id="Range"
-        message="Value must be ≥ 0 (was -1)"
-        errorLine1="                childHeightSpec = MeasureSpec.makeMeasureSpec(maxLayoutHeight, MeasureSpec.AT_MOST);"
-        errorLine2="                                                              ~~~~~~~~~~~~~~~">
-        <location
-            file="java/android/support/v4/widget/SlidingPaneLayout.java"
-            line="536"
-            column="63"/>
-    </issue>
-
-    <issue
-        id="Range"
-        message="Value must be ≥ 0 (was -1)"
-        errorLine1="                childHeightSpec = MeasureSpec.makeMeasureSpec(maxLayoutHeight, MeasureSpec.EXACTLY);"
-        errorLine2="                                                              ~~~~~~~~~~~~~~~">
-        <location
-            file="java/android/support/v4/widget/SlidingPaneLayout.java"
-            line="538"
-            column="63"/>
-    </issue>
-
-    <issue
-        id="Range"
-        message="Value must be ≥ 0 (was -1)"
-        errorLine1="                                childHeightSpec = MeasureSpec.makeMeasureSpec(maxLayoutHeight,"
-        errorLine2="                                                                              ~~~~~~~~~~~~~~~">
-        <location
-            file="java/android/support/v4/widget/SlidingPaneLayout.java"
-            line="586"
-            column="79"/>
-    </issue>
-
-    <issue
-        id="Range"
-        message="Value must be ≥ 0 (was -1)"
-        errorLine1="                                childHeightSpec = MeasureSpec.makeMeasureSpec(maxLayoutHeight,"
-        errorLine2="                                                                              ~~~~~~~~~~~~~~~">
-        <location
-            file="java/android/support/v4/widget/SlidingPaneLayout.java"
-            line="589"
-            column="79"/>
-    </issue>
-
-    <issue
-        id="Range"
-        message="Value must be ≥ 0 (was -1)"
-        errorLine1="                            childHeightSpec = MeasureSpec.makeMeasureSpec(maxLayoutHeight,"
-        errorLine2="                                                                          ~~~~~~~~~~~~~~~">
-        <location
-            file="java/android/support/v4/widget/SlidingPaneLayout.java"
-            line="608"
-            column="75"/>
-    </issue>
-
-    <issue
-        id="Range"
-        message="Value must be ≥ 0 (was -1)"
-        errorLine1="                            childHeightSpec = MeasureSpec.makeMeasureSpec(maxLayoutHeight,"
-        errorLine2="                                                                          ~~~~~~~~~~~~~~~">
-        <location
-            file="java/android/support/v4/widget/SlidingPaneLayout.java"
-            line="611"
-            column="75"/>
-    </issue>
-
-    <issue
         id="Suspicious0dp"
         message="Suspicious size: this will make the view invisible, should be used with `layout_weight`"
         errorLine1="        android:layout_width=&quot;0dp&quot;"
@@ -347,362 +248,6 @@
     </issue>
 
     <issue
-        id="LongLogTag"
-        message="The logging tag can be at most 23 characters, was 25 (FragmentStatePagerAdapter)"
-        errorLine1="        if (DEBUG) Log.v(TAG, &quot;Adding item #&quot; + position + &quot;: f=&quot; + fragment);"
-        errorLine2="                         ~~~">
-        <location
-            file="java/android/support/v4/app/FragmentStatePagerAdapter.java"
-            line="110"
-            column="26"/>
-    </issue>
-
-    <issue
-        id="LongLogTag"
-        message="The logging tag can be at most 23 characters, was 25 (FragmentStatePagerAdapter)"
-        errorLine1="        if (DEBUG) Log.v(TAG, &quot;Removing item #&quot; + position + &quot;: f=&quot; + object"
-        errorLine2="                         ~~~">
-        <location
-            file="java/android/support/v4/app/FragmentStatePagerAdapter.java"
-            line="135"
-            column="26"/>
-    </issue>
-
-    <issue
-        id="LongLogTag"
-        message="The logging tag can be at most 23 characters, was 25 (FragmentStatePagerAdapter)"
-        errorLine1="                        Log.w(TAG, &quot;Bad fragment at key &quot; + key);"
-        errorLine2="                              ~~~">
-        <location
-            file="java/android/support/v4/app/FragmentStatePagerAdapter.java"
-            line="224"
-            column="31"/>
-    </issue>
-
-    <issue
-        id="LongLogTag"
-        message="The logging tag can be at most 23 characters, was 28 (RoundedBitmapDrawableFactory)"
-        errorLine1="            Log.w(TAG, &quot;RoundedBitmapDrawable cannot decode &quot; + filepath);"
-        errorLine2="                  ~~~">
-        <location
-            file="java/android/support/v4/graphics/drawable/RoundedBitmapDrawableFactory.java"
-            line="80"
-            column="19"/>
-    </issue>
-
-    <issue
-        id="LongLogTag"
-        message="The logging tag can be at most 23 characters, was 28 (RoundedBitmapDrawableFactory)"
-        errorLine1="            Log.w(TAG, &quot;RoundedBitmapDrawable cannot decode &quot; + is);"
-        errorLine2="                  ~~~">
-        <location
-            file="java/android/support/v4/graphics/drawable/RoundedBitmapDrawableFactory.java"
-            line="93"
-            column="19"/>
-    </issue>
-
-    <issue
-        id="LongLogTag"
-        message="The logging tag can be at most 23 characters, was 26 (StaggeredGridLayoutManager)"
-        errorLine1="            Log.d(TAG, &quot;found saved state: &quot; + mPendingSavedState);"
-        errorLine2="                  ~~~">
-        <location
-            file="src/android/support/v7/widget/StaggeredGridLayoutManager.java"
-            line="783"
-            column="19"/>
-    </issue>
-
-    <issue
-        id="LongLogTag"
-        message="The logging tag can be at most 23 characters, was 26 (StaggeredGridLayoutManager)"
-        errorLine1="            Log.d(TAG, &quot;Deciding anchor info from fresh state&quot;);"
-        errorLine2="                  ~~~">
-        <location
-            file="src/android/support/v7/widget/StaggeredGridLayoutManager.java"
-            line="828"
-            column="19"/>
-    </issue>
-
-    <issue
-        id="LongLogTag"
-        message="The logging tag can be at most 23 characters, was 26 (StaggeredGridLayoutManager)"
-        errorLine1="            Log.d(TAG, &quot;invalid saved state class&quot;);"
-        errorLine2="                  ~~~">
-        <location
-            file="src/android/support/v7/widget/StaggeredGridLayoutManager.java"
-            line="1187"
-            column="19"/>
-    </issue>
-
-    <issue
-        id="LongLogTag"
-        message="The logging tag can be at most 23 characters, was 26 (StaggeredGridLayoutManager)"
-        errorLine1="            Log.d(TAG, &quot;saved state:\n&quot; + state);"
-        errorLine2="                  ~~~">
-        <location
-            file="src/android/support/v7/widget/StaggeredGridLayoutManager.java"
-            line="1236"
-            column="19"/>
-    </issue>
-
-    <issue
-        id="LongLogTag"
-        message="The logging tag can be at most 23 characters, was 26 (StaggeredGridLayoutManager)"
-        errorLine1="            Log.d(TAG, &quot;FILLING targetLine: &quot; + targetLine + &quot;,&quot;"
-        errorLine2="                  ~~~">
-        <location
-            file="src/android/support/v7/widget/StaggeredGridLayoutManager.java"
-            line="1559"
-            column="19"/>
-    </issue>
-
-    <issue
-        id="LongLogTag"
-        message="The logging tag can be at most 23 characters, was 26 (StaggeredGridLayoutManager)"
-        errorLine1="                    Log.d(TAG, &quot;assigned &quot; + currentSpan.mIndex + &quot; for &quot; + position);"
-        errorLine2="                          ~~~">
-        <location
-            file="src/android/support/v7/widget/StaggeredGridLayoutManager.java"
-            line="1580"
-            column="27"/>
-    </issue>
-
-    <issue
-        id="LongLogTag"
-        message="The logging tag can be at most 23 characters, was 26 (StaggeredGridLayoutManager)"
-        errorLine1="                    Log.d(TAG, &quot;using &quot; + spanIndex + &quot; for pos &quot; + position);"
-        errorLine2="                          ~~~">
-        <location
-            file="src/android/support/v7/widget/StaggeredGridLayoutManager.java"
-            line="1584"
-            column="27"/>
-    </issue>
-
-    <issue
-        id="LongLogTag"
-        message="The logging tag can be at most 23 characters, was 26 (StaggeredGridLayoutManager)"
-        errorLine1="            Log.d(TAG, &quot;asked &quot; + dt + &quot; scrolled&quot; + totalScroll);"
-        errorLine2="                  ~~~">
-        <location
-            file="src/android/support/v7/widget/StaggeredGridLayoutManager.java"
-            line="2153"
-            column="19"/>
-    </issue>
-
-    <issue
-        id="LongLogTag"
-        message="The logging tag can be at most 23 characters, was 26 (StaggeredGridLayoutManager)"
-        errorLine1="                    Log.d(TAG, &quot;Unknown focus request:&quot; + focusDirection);"
-        errorLine2="                          ~~~">
-        <location
-            file="src/android/support/v7/widget/StaggeredGridLayoutManager.java"
-            line="2385"
-            column="27"/>
-    </issue>
-
-    <issue
-        id="LongLogTag"
-        message="The logging tag can be at most 23 characters, was 24 (WakefulBroadcastReceiver)"
-        errorLine1="            Log.w(&quot;WakefulBroadcastReceiver&quot;, &quot;No active wake lock id #&quot; + id);"
-        errorLine2="                  ~~~~~~~~~~~~~~~~~~~~~~~~~~">
-        <location
-            file="java/android/support/v4/content/WakefulBroadcastReceiver.java"
-            line="141"
-            column="19"/>
-    </issue>
-
-    <issue
-        id="AppCompatCustomView"
-        message="This custom view should extend `android.support.v7.widget.AppCompatAutoCompleteTextView` instead"
-        errorLine1="public class AppCompatAutoCompleteTextView extends AutoCompleteTextView implements"
-        errorLine2="                                                   ~~~~~~~~~~~~~~~~~~~~">
-        <location
-            file="src/android/support/v7/widget/AppCompatAutoCompleteTextView.java"
-            line="49"
-            column="52"/>
-    </issue>
-
-    <issue
-        id="AppCompatCustomView"
-        message="This custom view should extend `android.support.v7.widget.AppCompatButton` instead"
-        errorLine1="public class AppCompatButton extends Button implements TintableBackgroundView {"
-        errorLine2="                                     ~~~~~~">
-        <location
-            file="src/android/support/v7/widget/AppCompatButton.java"
-            line="51"
-            column="38"/>
-    </issue>
-
-    <issue
-        id="AppCompatCustomView"
-        message="This custom view should extend `android.support.v7.widget.AppCompatCheckBox` instead"
-        errorLine1="public class AppCompatCheckBox extends CheckBox implements TintableCompoundButton {"
-        errorLine2="                                       ~~~~~~~~">
-        <location
-            file="src/android/support/v7/widget/AppCompatCheckBox.java"
-            line="49"
-            column="40"/>
-    </issue>
-
-    <issue
-        id="AppCompatCustomView"
-        message="This custom view should extend `android.support.v7.widget.AppCompatCheckedTextView` instead"
-        errorLine1="public class AppCompatCheckedTextView extends CheckedTextView {"
-        errorLine2="                                              ~~~~~~~~~~~~~~~">
-        <location
-            file="src/android/support/v7/widget/AppCompatCheckedTextView.java"
-            line="33"
-            column="47"/>
-    </issue>
-
-    <issue
-        id="AppCompatCustomView"
-        message="This custom view should extend `android.support.v7.widget.AppCompatEditText` instead"
-        errorLine1="public class AppCompatEditText extends EditText implements TintableBackgroundView {"
-        errorLine2="                                       ~~~~~~~~">
-        <location
-            file="src/android/support/v7/widget/AppCompatEditText.java"
-            line="48"
-            column="40"/>
-    </issue>
-
-    <issue
-        id="AppCompatCustomView"
-        message="This custom view should extend `android.support.v7.widget.AppCompatImageButton` instead"
-        errorLine1="public class AppCompatImageButton extends ImageButton implements TintableBackgroundView,"
-        errorLine2="                                          ~~~~~~~~~~~">
-        <location
-            file="src/android/support/v7/widget/AppCompatImageButton.java"
-            line="58"
-            column="43"/>
-    </issue>
-
-    <issue
-        id="AppCompatCustomView"
-        message="This custom view should extend `android.support.v7.widget.AppCompatImageView` instead"
-        errorLine1="public class AppCompatImageView extends ImageView implements TintableBackgroundView,"
-        errorLine2="                                        ~~~~~~~~~">
-        <location
-            file="src/android/support/v7/widget/AppCompatImageView.java"
-            line="57"
-            column="41"/>
-    </issue>
-
-    <issue
-        id="AppCompatCustomView"
-        message="This custom view should extend `android.support.v7.widget.AppCompatMultiAutoCompleteTextView` instead"
-        errorLine1="public class AppCompatMultiAutoCompleteTextView extends MultiAutoCompleteTextView"
-        errorLine2="                                                        ~~~~~~~~~~~~~~~~~~~~~~~~~">
-        <location
-            file="src/android/support/v7/widget/AppCompatMultiAutoCompleteTextView.java"
-            line="49"
-            column="57"/>
-    </issue>
-
-    <issue
-        id="AppCompatCustomView"
-        message="This custom view should extend `android.support.v7.widget.AppCompatRadioButton` instead"
-        errorLine1="public class AppCompatRadioButton extends RadioButton implements TintableCompoundButton {"
-        errorLine2="                                          ~~~~~~~~~~~">
-        <location
-            file="src/android/support/v7/widget/AppCompatRadioButton.java"
-            line="49"
-            column="43"/>
-    </issue>
-
-    <issue
-        id="AppCompatCustomView"
-        message="This custom view should extend `android.support.v7.widget.AppCompatRatingBar` instead"
-        errorLine1="public class AppCompatRatingBar extends RatingBar {"
-        errorLine2="                                        ~~~~~~~~~">
-        <location
-            file="src/android/support/v7/widget/AppCompatRatingBar.java"
-            line="34"
-            column="41"/>
-    </issue>
-
-    <issue
-        id="AppCompatCustomView"
-        message="This custom view should extend `android.support.v7.widget.AppCompatSeekBar` instead"
-        errorLine1="public class AppCompatSeekBar extends SeekBar {"
-        errorLine2="                                      ~~~~~~~">
-        <location
-            file="src/android/support/v7/widget/AppCompatSeekBar.java"
-            line="34"
-            column="39"/>
-    </issue>
-
-    <issue
-        id="AppCompatCustomView"
-        message="This custom view should extend `android.support.v7.widget.AppCompatSpinner` instead"
-        errorLine1="public class AppCompatSpinner extends Spinner implements TintableBackgroundView {"
-        errorLine2="                                      ~~~~~~~">
-        <location
-            file="src/android/support/v7/widget/AppCompatSpinner.java"
-            line="68"
-            column="39"/>
-    </issue>
-
-    <issue
-        id="AppCompatCustomView"
-        message="This custom view should extend `android.support.v7.widget.AppCompatTextView` instead"
-        errorLine1="public class AppCompatTextView extends TextView implements TintableBackgroundView,"
-        errorLine2="                                       ~~~~~~~~">
-        <location
-            file="src/android/support/v7/widget/AppCompatTextView.java"
-            line="60"
-            column="40"/>
-    </issue>
-
-    <issue
-        id="AppCompatCustomView"
-        message="This custom view should extend `android.support.v7.widget.AppCompatImageView` instead"
-        errorLine1="class CircleImageView extends ImageView {"
-        errorLine2="                              ~~~~~~~~~">
-        <location
-            file="java/android/support/v4/widget/CircleImageView.java"
-            line="38"
-            column="31"/>
-    </issue>
-
-    <issue
-        id="AppCompatCustomView"
-        message="This custom view should extend `android.support.v7.widget.AppCompatTextView` instead"
-        errorLine1="public class DialogTitle extends TextView {"
-        errorLine2="                                 ~~~~~~~~">
-        <location
-            file="src/android/support/v7/widget/DialogTitle.java"
-            line="37"
-            column="34"/>
-    </issue>
-
-    <issue
-        id="AppCompatCustomView"
-        message="This custom view should extend `android.support.v7.widget.AppCompatImageView` instead"
-        errorLine1="public class PreferenceImageView extends ImageView {"
-        errorLine2="                                         ~~~~~~~~~">
-        <location
-            file="src/android/support/v7/internal/widget/PreferenceImageView.java"
-            line="33"
-            column="42"/>
-    </issue>
-
-    <issue
-        id="UniqueConstants"
-        message="Constants `FLAG_CVE_EQ_PVE` and `FLAG_CVE_EQ_PVE` specify the same exact value (8192); this is usually a cut &amp; paste or merge error"
-        errorLine1="            FLAG_CVE_EQ_PVE, FLAG_CVE_EQ_PVE, FLAG_CVE_LT_PVE"
-        errorLine2="                             ~~~~~~~~~~~~~~~">
-        <location
-            file="src/android/support/v7/widget/ViewBoundsCheck.java"
-            line="125"
-            column="30"/>
-        <location
-            file="src/android/support/v7/widget/ViewBoundsCheck.java"
-            line="125"
-            column="13"/>
-    </issue>
-
-    <issue
         id="WrongConstant"
         message="Must be one of: PixelFormat.UNKNOWN, PixelFormat.TRANSLUCENT, PixelFormat.TRANSPARENT, PixelFormat.OPAQUE"
         errorLine1="        return 0;"
@@ -715,35 +260,13 @@
 
     <issue
         id="WrongConstant"
-        message="Must be one of: Gravity.LEFT, Gravity.RIGHT, GravityCompat.START, GravityCompat.END"
-        errorLine1="        setDrawerLockMode(lockMode, gravity);"
-        errorLine2="                                    ~~~~~~~">
+        message="Must be one or more of: Gravity.LEFT, Gravity.RIGHT, GravityCompat.START, GravityCompat.END"
+        errorLine1="                            + gravityToString(childGravity) + &quot; but this &quot; + TAG + &quot; already has a &quot;"
+        errorLine2="                                              ~~~~~~~~~~~~">
         <location
             file="java/android/support/v4/widget/DrawerLayout.java"
-            line="680"
-            column="37"/>
-    </issue>
-
-    <issue
-        id="WrongConstant"
-        message="Must be one of: Gravity.LEFT, Gravity.RIGHT, GravityCompat.START, GravityCompat.END"
-        errorLine1="        return getDrawerLockMode(drawerGravity);"
-        errorLine2="                                 ~~~~~~~~~~~~~">
-        <location
-            file="java/android/support/v4/widget/DrawerLayout.java"
-            line="753"
-            column="34"/>
-    </issue>
-
-    <issue
-        id="WrongConstant"
-        message="Flag not allowed here"
-        errorLine1="                        getDrawerViewAbsoluteGravity(child) &amp; Gravity.HORIZONTAL_GRAVITY_MASK;"
-        errorLine2="                        ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
-        <location
-            file="java/android/support/v4/widget/DrawerLayout.java"
-            line="1086"
-            column="25"/>
+            line="1075"
+            column="47"/>
     </issue>
 
     <issue
@@ -768,15 +291,4 @@
             column="36"/>
     </issue>
 
-    <issue
-        id="WrongConstant"
-        message="Must be one of: View.LAYOUT_DIRECTION_LTR, View.LAYOUT_DIRECTION_RTL"
-        errorLine1="            return isAutoMirrored() &amp;&amp; getLayoutDirection() == LayoutDirection.RTL;"
-        errorLine2="                                                               ~~~~~~~~~~~~~~~~~~~">
-        <location
-            file="src/android/support/graphics/drawable/VectorDrawableCompat.java"
-            line="821"
-            column="64"/>
-    </issue>
-
 </issues>
diff --git a/v17/leanback/build.gradle b/v17/leanback/build.gradle
index 8c02cc4..a2e7342 100644
--- a/v17/leanback/build.gradle
+++ b/v17/leanback/build.gradle
@@ -36,11 +36,6 @@
         ]
         main.res.srcDir 'res'
     }
-
-    lintOptions {
-        // Remove this once all NewApi breakages have been fixed.
-        disable "NewApi"
-    }
 }
 
 supportLibrary {
diff --git a/v17/leanback/kitkat/android/support/v17/leanback/transition/LeanbackTransitionHelperKitKat.java b/v17/leanback/kitkat/android/support/v17/leanback/transition/LeanbackTransitionHelperKitKat.java
index 872e836..bb30b00 100644
--- a/v17/leanback/kitkat/android/support/v17/leanback/transition/LeanbackTransitionHelperKitKat.java
+++ b/v17/leanback/kitkat/android/support/v17/leanback/transition/LeanbackTransitionHelperKitKat.java
@@ -35,7 +35,7 @@
         SlideKitkat slide = new SlideKitkat();
         slide.setSlideEdge(Gravity.TOP);
         slide.setInterpolator(AnimationUtils.loadInterpolator(context,
-                R.animator.lb_decelerator_4));
+                R.anim.lb_decelerator_4));
         slide.addTarget(R.id.browse_title_group);
         return slide;
     }
diff --git a/v17/leanback/lint-baseline.xml b/v17/leanback/lint-baseline.xml
index d87e4b2..b373b5e 100644
--- a/v17/leanback/lint-baseline.xml
+++ b/v17/leanback/lint-baseline.xml
@@ -1,683 +1,15 @@
 <?xml version="1.0" encoding="UTF-8"?>
-<issues format="4" by="lint 2.4.0-alpha6">
-
-    <issue
-        id="MissingSuperCall"
-        message="Overriding method should call `super.onAnimationEnd`"
-        errorLine1="                public void onAnimationEnd(Animation animation) {"
-        errorLine2="                            ~~~~~~~~~~~~~~">
-        <location
-            file="java/android/support/v4/app/FragmentManager.java"
-            line="1604"
-            column="29"/>
-    </issue>
-
-    <issue
-        id="MissingSuperCall"
-        message="Overriding method should call `super.onAnimationEnd`"
-        errorLine1="        public void onAnimationEnd(Animation animation) {"
-        errorLine2="                    ~~~~~~~~~~~~~~">
-        <location
-            file="java/android/support/v4/app/FragmentManager.java"
-            line="3935"
-            column="21"/>
-    </issue>
-
-    <issue
-        id="ResourceType"
-        message="Expected resource of type string"
-        errorLine1="                        ? Float.parseFloat(res.getString(R.dimen.lb_browse_header_select_scale))"
-        errorLine2="                                                         ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
-        <location
-            file="src/android/support/v17/leanback/widget/FocusHighlightHelper.java"
-            line="276"
-            column="58"/>
-    </issue>
-
-    <issue
-        id="ResourceType"
-        message="Expected resource of type string"
-        errorLine1="                        Integer.parseInt(res.getString(R.dimen.lb_browse_header_select_duration));"
-        errorLine2="                                                       ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
-        <location
-            file="src/android/support/v17/leanback/widget/FocusHighlightHelper.java"
-            line="279"
-            column="56"/>
-    </issue>
-
-    <issue
-        id="ResourceType"
-        message="Expected resource of type anim or interpolator"
-        errorLine1="                R.animator.lb_decelerator_4));"
-        errorLine2="                ~~~~~~~~~~~~~~~~~~~~~~~~~~~">
-        <location
-            file="kitkat/android/support/v17/leanback/transition/LeanbackTransitionHelperKitKat.java"
-            line="38"
-            column="17"/>
-    </issue>
-
-    <issue
-        id="Range"
-        message="Value must be ≥ 0 (was -1)"
-        errorLine1="                childHeightSpec = MeasureSpec.makeMeasureSpec(maxLayoutHeight, MeasureSpec.AT_MOST);"
-        errorLine2="                                                              ~~~~~~~~~~~~~~~">
-        <location
-            file="java/android/support/v4/widget/SlidingPaneLayout.java"
-            line="536"
-            column="63"/>
-    </issue>
-
-    <issue
-        id="Range"
-        message="Value must be ≥ 0 (was -1)"
-        errorLine1="                childHeightSpec = MeasureSpec.makeMeasureSpec(maxLayoutHeight, MeasureSpec.EXACTLY);"
-        errorLine2="                                                              ~~~~~~~~~~~~~~~">
-        <location
-            file="java/android/support/v4/widget/SlidingPaneLayout.java"
-            line="538"
-            column="63"/>
-    </issue>
-
-    <issue
-        id="Range"
-        message="Value must be ≥ 0 (was -1)"
-        errorLine1="                                childHeightSpec = MeasureSpec.makeMeasureSpec(maxLayoutHeight,"
-        errorLine2="                                                                              ~~~~~~~~~~~~~~~">
-        <location
-            file="java/android/support/v4/widget/SlidingPaneLayout.java"
-            line="586"
-            column="79"/>
-    </issue>
-
-    <issue
-        id="Range"
-        message="Value must be ≥ 0 (was -1)"
-        errorLine1="                                childHeightSpec = MeasureSpec.makeMeasureSpec(maxLayoutHeight,"
-        errorLine2="                                                                              ~~~~~~~~~~~~~~~">
-        <location
-            file="java/android/support/v4/widget/SlidingPaneLayout.java"
-            line="589"
-            column="79"/>
-    </issue>
-
-    <issue
-        id="Range"
-        message="Value must be ≥ 0 (was -1)"
-        errorLine1="                            childHeightSpec = MeasureSpec.makeMeasureSpec(maxLayoutHeight,"
-        errorLine2="                                                                          ~~~~~~~~~~~~~~~">
-        <location
-            file="java/android/support/v4/widget/SlidingPaneLayout.java"
-            line="608"
-            column="75"/>
-    </issue>
-
-    <issue
-        id="Range"
-        message="Value must be ≥ 0 (was -1)"
-        errorLine1="                            childHeightSpec = MeasureSpec.makeMeasureSpec(maxLayoutHeight,"
-        errorLine2="                                                                          ~~~~~~~~~~~~~~~">
-        <location
-            file="java/android/support/v4/widget/SlidingPaneLayout.java"
-            line="611"
-            column="75"/>
-    </issue>
-
-    <issue
-        id="LongLogTag"
-        message="The logging tag can be at most 23 characters, was 27 (DetailsOverviewRowPresenter)"
-        errorLine1="                if (DEBUG) Log.v(TAG, &quot;onLayoutChange &quot; + v);"
-        errorLine2="                                 ~~~">
-        <location
-            file="src/android/support/v17/leanback/widget/DetailsOverviewRowPresenter.java"
-            line="178"
-            column="34"/>
-    </issue>
-
-    <issue
-        id="LongLogTag"
-        message="The logging tag can be at most 23 characters, was 27 (DetailsOverviewRowPresenter)"
-        errorLine1="                Log.v(TAG, &quot;checkFirstAndLast fromScroll &quot; + fromScroll"
-        errorLine2="                      ~~~">
-        <location
-            file="src/android/support/v17/leanback/widget/DetailsOverviewRowPresenter.java"
-            line="237"
-            column="23"/>
-    </issue>
-
-    <issue
-        id="LongLogTag"
-        message="The logging tag can be at most 23 characters, was 25 (FragmentStatePagerAdapter)"
-        errorLine1="        if (DEBUG) Log.v(TAG, &quot;Adding item #&quot; + position + &quot;: f=&quot; + fragment);"
-        errorLine2="                         ~~~">
-        <location
-            file="java/android/support/v4/app/FragmentStatePagerAdapter.java"
-            line="110"
-            column="26"/>
-    </issue>
-
-    <issue
-        id="LongLogTag"
-        message="The logging tag can be at most 23 characters, was 25 (FragmentStatePagerAdapter)"
-        errorLine1="        if (DEBUG) Log.v(TAG, &quot;Removing item #&quot; + position + &quot;: f=&quot; + object"
-        errorLine2="                         ~~~">
-        <location
-            file="java/android/support/v4/app/FragmentStatePagerAdapter.java"
-            line="135"
-            column="26"/>
-    </issue>
-
-    <issue
-        id="LongLogTag"
-        message="The logging tag can be at most 23 characters, was 25 (FragmentStatePagerAdapter)"
-        errorLine1="                        Log.w(TAG, &quot;Bad fragment at key &quot; + key);"
-        errorLine2="                              ~~~">
-        <location
-            file="java/android/support/v4/app/FragmentStatePagerAdapter.java"
-            line="224"
-            column="31"/>
-    </issue>
-
-    <issue
-        id="LongLogTag"
-        message="The logging tag can be at most 23 characters, was 36 (FullWidthDetailsOverviewRowPresenter)"
-        errorLine1="                if (DEBUG) Log.v(TAG, &quot;onLayoutChange &quot; + v);"
-        errorLine2="                                 ~~~">
-        <location
-            file="src/android/support/v17/leanback/widget/FullWidthDetailsOverviewRowPresenter.java"
-            line="236"
-            column="34"/>
-    </issue>
-
-    <issue
-        id="LongLogTag"
-        message="The logging tag can be at most 23 characters, was 36 (FullWidthDetailsOverviewRowPresenter)"
-        errorLine1="                Log.v(TAG, &quot;checkFirstAndLast fromScroll &quot; + fromScroll"
-        errorLine2="                      ~~~">
-        <location
-            file="src/android/support/v17/leanback/widget/FullWidthDetailsOverviewRowPresenter.java"
-            line="295"
-            column="23"/>
-    </issue>
-
-    <issue
-        id="LongLogTag"
-        message="The logging tag can be at most 23 characters, was 25 (GuidedStepSupportFragment)"
-        errorLine1="            Log.w(TAG, &quot;Fragment is already exists, likely calling &quot;"
-        errorLine2="                  ~~~">
-        <location
-            file="src/android/support/v17/leanback/app/GuidedStepSupportFragment.java"
-            line="641"
-            column="19"/>
-    </issue>
-
-    <issue
-        id="LongLogTag"
-        message="The logging tag can be at most 23 characters, was 25 (GuidedStepSupportFragment)"
-        errorLine1="        if (DEBUG) Log.v(TAG, &quot;onCreate&quot;);"
-        errorLine2="                         ~~~">
-        <location
-            file="src/android/support/v17/leanback/app/GuidedStepSupportFragment.java"
-            line="999"
-            column="26"/>
-    </issue>
-
-    <issue
-        id="LongLogTag"
-        message="The logging tag can be at most 23 characters, was 25 (GuidedStepSupportFragment)"
-        errorLine1="        if (DEBUG) Log.v(TAG, &quot;onCreateView&quot;);"
-        errorLine2="                         ~~~">
-        <location
-            file="src/android/support/v17/leanback/app/GuidedStepSupportFragment.java"
-            line="1038"
-            column="26"/>
-    </issue>
-
-    <issue
-        id="LongLogTag"
-        message="The logging tag can be at most 23 characters, was 25 (GuidedStepSupportFragment)"
-        errorLine1="        if (DEBUG) Log.v(TAG, &quot;Found guided step theme flag? &quot; + found);"
-        errorLine2="                         ~~~">
-        <location
-            file="src/android/support/v17/leanback/app/GuidedStepSupportFragment.java"
-            line="1237"
-            column="26"/>
-    </issue>
-
-    <issue
-        id="LongLogTag"
-        message="The logging tag can be at most 23 characters, was 25 (GuidedStepSupportFragment)"
-        errorLine1="            if (DEBUG) Log.v(TAG, &quot;Found guided step theme reference? &quot; + found);"
-        errorLine2="                             ~~~">
-        <location
-            file="src/android/support/v17/leanback/app/GuidedStepSupportFragment.java"
-            line="1350"
-            column="30"/>
-    </issue>
-
-    <issue
-        id="LongLogTag"
-        message="The logging tag can be at most 23 characters, was 25 (GuidedStepSupportFragment)"
-        errorLine1="                Log.e(TAG, &quot;GuidedStepSupportFragment does not have an appropriate theme set.&quot;);"
-        errorLine2="                      ~~~">
-        <location
-            file="src/android/support/v17/leanback/app/GuidedStepSupportFragment.java"
-            line="1362"
-            column="23"/>
-    </issue>
-
-    <issue
-        id="LongLogTag"
-        message="The logging tag can be at most 23 characters, was 25 (OnboardingSupportFragment)"
-        errorLine1="            if (DEBUG) Log.v(TAG, &quot;Found onboarding theme reference? &quot; + found);"
-        errorLine2="                             ~~~">
-        <location
-            file="src/android/support/v17/leanback/app/OnboardingSupportFragment.java"
-            line="552"
-            column="30"/>
-    </issue>
-
-    <issue
-        id="LongLogTag"
-        message="The logging tag can be at most 23 characters, was 30 (PlaybackOverlaySupportFragment)"
-        errorLine1="            if (DEBUG) Log.v(TAG, &quot;onAnimationEnd &quot; + mBgAlpha);"
-        errorLine2="                             ~~~">
-        <location
-            file="src/android/support/v17/leanback/app/PlaybackOverlaySupportFragment.java"
-            line="148"
-            column="30"/>
-    </issue>
-
-    <issue
-        id="LongLogTag"
-        message="The logging tag can be at most 23 characters, was 30 (PlaybackOverlaySupportFragment)"
-        errorLine1="        if (DEBUG) Log.v(TAG, &quot;setFadingEnabled &quot; + enabled);"
-        errorLine2="                         ~~~">
-        <location
-            file="src/android/support/v17/leanback/app/PlaybackOverlaySupportFragment.java"
-            line="236"
-            column="26"/>
-    </issue>
-
-    <issue
-        id="LongLogTag"
-        message="The logging tag can be at most 23 characters, was 30 (PlaybackOverlaySupportFragment)"
-        errorLine1="        if (DEBUG) Log.v(TAG, &quot;tickle enabled &quot; + mFadingEnabled + &quot; isResumed &quot; + isResumed());"
-        errorLine2="                         ~~~">
-        <location
-            file="src/android/support/v17/leanback/app/PlaybackOverlaySupportFragment.java"
-            line="313"
-            column="26"/>
-    </issue>
-
-    <issue
-        id="LongLogTag"
-        message="The logging tag can be at most 23 characters, was 30 (PlaybackOverlaySupportFragment)"
-        errorLine1="        if (DEBUG) Log.v(TAG, &quot;onInterceptInputEvent hidden &quot; + controlsHidden + &quot; &quot; + event);"
-        errorLine2="                         ~~~">
-        <location
-            file="src/android/support/v17/leanback/app/PlaybackOverlaySupportFragment.java"
-            line="363"
-            column="26"/>
-    </issue>
-
-    <issue
-        id="LongLogTag"
-        message="The logging tag can be at most 23 characters, was 30 (PlaybackOverlaySupportFragment)"
-        errorLine1="                    if (DEBUG) Log.v(TAG, &quot;fraction &quot; + fraction);"
-        errorLine2="                                     ~~~">
-        <location
-            file="src/android/support/v17/leanback/app/PlaybackOverlaySupportFragment.java"
-            line="481"
-            column="38"/>
-    </issue>
-
-    <issue
-        id="LongLogTag"
-        message="The logging tag can be at most 23 characters, was 30 (PlaybackOverlaySupportFragment)"
-        errorLine1="        if (DEBUG) Log.v(TAG, &quot;fade &quot; + fadeIn);"
-        errorLine2="                         ~~~">
-        <location
-            file="src/android/support/v17/leanback/app/PlaybackOverlaySupportFragment.java"
-            line="577"
-            column="26"/>
-    </issue>
-
-    <issue
-        id="LongLogTag"
-        message="The logging tag can be at most 23 characters, was 30 (PlaybackOverlaySupportFragment)"
-        errorLine1="            if (DEBUG) Log.v(TAG, &quot;requested fade in progress&quot;);"
-        errorLine2="                             ~~~">
-        <location
-            file="src/android/support/v17/leanback/app/PlaybackOverlaySupportFragment.java"
-            line="582"
-            column="30"/>
-    </issue>
-
-    <issue
-        id="LongLogTag"
-        message="The logging tag can be at most 23 characters, was 30 (PlaybackOverlaySupportFragment)"
-        errorLine1="            if (DEBUG) Log.v(TAG, &quot;fade is no-op&quot;);"
-        errorLine2="                             ~~~">
-        <location
-            file="src/android/support/v17/leanback/app/PlaybackOverlaySupportFragment.java"
-            line="586"
-            column="30"/>
-    </issue>
-
-    <issue
-        id="LongLogTag"
-        message="The logging tag can be at most 23 characters, was 30 (PlaybackOverlaySupportFragment)"
-        errorLine1="            if (DEBUG) Log.v(TAG, &quot;onAttachedToWindow &quot; + vh.getViewHolder().view);"
-        errorLine2="                             ~~~">
-        <location
-            file="src/android/support/v17/leanback/app/PlaybackOverlaySupportFragment.java"
-            line="770"
-            column="30"/>
-    </issue>
-
-    <issue
-        id="LongLogTag"
-        message="The logging tag can be at most 23 characters, was 30 (PlaybackOverlaySupportFragment)"
-        errorLine1="                if (DEBUG) Log.v(TAG, &quot;setting alpha to 0&quot;);"
-        errorLine2="                                 ~~~">
-        <location
-            file="src/android/support/v17/leanback/app/PlaybackOverlaySupportFragment.java"
-            line="772"
-            column="34"/>
-    </issue>
-
-    <issue
-        id="LongLogTag"
-        message="The logging tag can be at most 23 characters, was 30 (PlaybackOverlaySupportFragment)"
-        errorLine1="            if (DEBUG) Log.v(TAG, &quot;onDetachedFromWindow &quot; + vh.getViewHolder().view);"
-        errorLine2="                             ~~~">
-        <location
-            file="src/android/support/v17/leanback/app/PlaybackOverlaySupportFragment.java"
-            line="781"
-            column="30"/>
-    </issue>
-
-    <issue
-        id="LongLogTag"
-        message="The logging tag can be at most 23 characters, was 28 (RoundedBitmapDrawableFactory)"
-        errorLine1="            Log.w(TAG, &quot;RoundedBitmapDrawable cannot decode &quot; + filepath);"
-        errorLine2="                  ~~~">
-        <location
-            file="java/android/support/v4/graphics/drawable/RoundedBitmapDrawableFactory.java"
-            line="80"
-            column="19"/>
-    </issue>
-
-    <issue
-        id="LongLogTag"
-        message="The logging tag can be at most 23 characters, was 28 (RoundedBitmapDrawableFactory)"
-        errorLine1="            Log.w(TAG, &quot;RoundedBitmapDrawable cannot decode &quot; + is);"
-        errorLine2="                  ~~~">
-        <location
-            file="java/android/support/v4/graphics/drawable/RoundedBitmapDrawableFactory.java"
-            line="93"
-            column="19"/>
-    </issue>
-
-    <issue
-        id="LongLogTag"
-        message="The logging tag can be at most 23 characters, was 26 (StaggeredGridLayoutManager)"
-        errorLine1="            Log.d(TAG, &quot;found saved state: &quot; + mPendingSavedState);"
-        errorLine2="                  ~~~">
-        <location
-            file="src/android/support/v7/widget/StaggeredGridLayoutManager.java"
-            line="783"
-            column="19"/>
-    </issue>
-
-    <issue
-        id="LongLogTag"
-        message="The logging tag can be at most 23 characters, was 26 (StaggeredGridLayoutManager)"
-        errorLine1="            Log.d(TAG, &quot;Deciding anchor info from fresh state&quot;);"
-        errorLine2="                  ~~~">
-        <location
-            file="src/android/support/v7/widget/StaggeredGridLayoutManager.java"
-            line="828"
-            column="19"/>
-    </issue>
-
-    <issue
-        id="LongLogTag"
-        message="The logging tag can be at most 23 characters, was 26 (StaggeredGridLayoutManager)"
-        errorLine1="            Log.d(TAG, &quot;invalid saved state class&quot;);"
-        errorLine2="                  ~~~">
-        <location
-            file="src/android/support/v7/widget/StaggeredGridLayoutManager.java"
-            line="1187"
-            column="19"/>
-    </issue>
-
-    <issue
-        id="LongLogTag"
-        message="The logging tag can be at most 23 characters, was 26 (StaggeredGridLayoutManager)"
-        errorLine1="            Log.d(TAG, &quot;saved state:\n&quot; + state);"
-        errorLine2="                  ~~~">
-        <location
-            file="src/android/support/v7/widget/StaggeredGridLayoutManager.java"
-            line="1236"
-            column="19"/>
-    </issue>
-
-    <issue
-        id="LongLogTag"
-        message="The logging tag can be at most 23 characters, was 26 (StaggeredGridLayoutManager)"
-        errorLine1="            Log.d(TAG, &quot;FILLING targetLine: &quot; + targetLine + &quot;,&quot;"
-        errorLine2="                  ~~~">
-        <location
-            file="src/android/support/v7/widget/StaggeredGridLayoutManager.java"
-            line="1559"
-            column="19"/>
-    </issue>
-
-    <issue
-        id="LongLogTag"
-        message="The logging tag can be at most 23 characters, was 26 (StaggeredGridLayoutManager)"
-        errorLine1="                    Log.d(TAG, &quot;assigned &quot; + currentSpan.mIndex + &quot; for &quot; + position);"
-        errorLine2="                          ~~~">
-        <location
-            file="src/android/support/v7/widget/StaggeredGridLayoutManager.java"
-            line="1580"
-            column="27"/>
-    </issue>
-
-    <issue
-        id="LongLogTag"
-        message="The logging tag can be at most 23 characters, was 26 (StaggeredGridLayoutManager)"
-        errorLine1="                    Log.d(TAG, &quot;using &quot; + spanIndex + &quot; for pos &quot; + position);"
-        errorLine2="                          ~~~">
-        <location
-            file="src/android/support/v7/widget/StaggeredGridLayoutManager.java"
-            line="1584"
-            column="27"/>
-    </issue>
-
-    <issue
-        id="LongLogTag"
-        message="The logging tag can be at most 23 characters, was 26 (StaggeredGridLayoutManager)"
-        errorLine1="            Log.d(TAG, &quot;asked &quot; + dt + &quot; scrolled&quot; + totalScroll);"
-        errorLine2="                  ~~~">
-        <location
-            file="src/android/support/v7/widget/StaggeredGridLayoutManager.java"
-            line="2153"
-            column="19"/>
-    </issue>
-
-    <issue
-        id="LongLogTag"
-        message="The logging tag can be at most 23 characters, was 26 (StaggeredGridLayoutManager)"
-        errorLine1="                    Log.d(TAG, &quot;Unknown focus request:&quot; + focusDirection);"
-        errorLine2="                          ~~~">
-        <location
-            file="src/android/support/v7/widget/StaggeredGridLayoutManager.java"
-            line="2385"
-            column="27"/>
-    </issue>
-
-    <issue
-        id="LongLogTag"
-        message="The logging tag can be at most 23 characters, was 27 (VerticalGridSupportFragment)"
-        errorLine1="            if (DEBUG) Log.v(TAG, &quot;grid selected position &quot; + position);"
-        errorLine2="                             ~~~">
-        <location
-            file="src/android/support/v17/leanback/app/VerticalGridSupportFragment.java"
-            line="120"
-            column="30"/>
-    </issue>
-
-    <issue
-        id="LongLogTag"
-        message="The logging tag can be at most 23 characters, was 24 (WakefulBroadcastReceiver)"
-        errorLine1="            Log.w(&quot;WakefulBroadcastReceiver&quot;, &quot;No active wake lock id #&quot; + id);"
-        errorLine2="                  ~~~~~~~~~~~~~~~~~~~~~~~~~~">
-        <location
-            file="java/android/support/v4/content/WakefulBroadcastReceiver.java"
-            line="141"
-            column="19"/>
-    </issue>
-
-    <issue
-        id="UniqueConstants"
-        message="Constants `FLAG_CVE_EQ_PVE` and `FLAG_CVE_EQ_PVE` specify the same exact value (8192); this is usually a cut &amp; paste or merge error"
-        errorLine1="            FLAG_CVE_EQ_PVE, FLAG_CVE_EQ_PVE, FLAG_CVE_LT_PVE"
-        errorLine2="                             ~~~~~~~~~~~~~~~">
-        <location
-            file="src/android/support/v7/widget/ViewBoundsCheck.java"
-            line="125"
-            column="30"/>
-        <location
-            file="src/android/support/v7/widget/ViewBoundsCheck.java"
-            line="125"
-            column="13"/>
-    </issue>
-
-    <issue
-        id="Orientation"
-        message="No orientation specified, and the default is horizontal. This is a common source of bugs when children are added dynamically."
-        errorLine1="            &lt;LinearLayout"
-        errorLine2="            ^">
-        <location
-            file="res/layout/lb_row_media_item.xml"
-            line="63"
-            column="13"/>
-    </issue>
+<issues format="4" by="lint 3.0.0-alpha5">
 
     <issue
         id="WrongConstant"
-        message="Must be one of: ViewCompat.LAYOUT_DIRECTION_LTR, ViewCompat.LAYOUT_DIRECTION_RTL"
-        errorLine1="            boolean isRtl = ViewCompat.getLayoutDirection(focused) == View.LAYOUT_DIRECTION_RTL;"
-        errorLine2="                                                                      ~~~~~~~~~~~~~~~~~~~~~~~~~">
-        <location
-            file="src/android/support/v17/leanback/app/BrowseFragment.java"
-            line="1029"
-            column="71"/>
-    </issue>
-
-    <issue
-        id="WrongConstant"
-        message="Must be one of: ViewCompat.LAYOUT_DIRECTION_LTR, ViewCompat.LAYOUT_DIRECTION_RTL"
-        errorLine1="            boolean isRtl = ViewCompat.getLayoutDirection(focused) == View.LAYOUT_DIRECTION_RTL;"
-        errorLine2="                                                                      ~~~~~~~~~~~~~~~~~~~~~~~~~">
-        <location
-            file="src/android/support/v17/leanback/app/BrowseSupportFragment.java"
-            line="1032"
-            column="71"/>
-    </issue>
-
-    <issue
-        id="WrongConstant"
-        message="Must be one of: Gravity.LEFT, Gravity.RIGHT, GravityCompat.START, GravityCompat.END"
-        errorLine1="        setDrawerLockMode(lockMode, gravity);"
-        errorLine2="                                    ~~~~~~~">
+        message="Must be one or more of: Gravity.LEFT, Gravity.RIGHT, GravityCompat.START, GravityCompat.END"
+        errorLine1="                            + gravityToString(childGravity) + &quot; but this &quot; + TAG + &quot; already has a &quot;"
+        errorLine2="                                              ~~~~~~~~~~~~">
         <location
             file="java/android/support/v4/widget/DrawerLayout.java"
-            line="680"
-            column="37"/>
-    </issue>
-
-    <issue
-        id="WrongConstant"
-        message="Must be one of: Gravity.LEFT, Gravity.RIGHT, GravityCompat.START, GravityCompat.END"
-        errorLine1="        return getDrawerLockMode(drawerGravity);"
-        errorLine2="                                 ~~~~~~~~~~~~~">
-        <location
-            file="java/android/support/v4/widget/DrawerLayout.java"
-            line="753"
-            column="34"/>
-    </issue>
-
-    <issue
-        id="WrongConstant"
-        message="Flag not allowed here"
-        errorLine1="                        getDrawerViewAbsoluteGravity(child) &amp; Gravity.HORIZONTAL_GRAVITY_MASK;"
-        errorLine2="                        ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
-        <location
-            file="java/android/support/v4/widget/DrawerLayout.java"
-            line="1086"
-            column="25"/>
-    </issue>
-
-    <issue
-        id="WrongConstant"
-        message="Must be one of: View.VISIBLE, View.INVISIBLE, View.GONE"
-        errorLine1="                    v.setVisibility(mChildVisibility);"
-        errorLine2="                                    ~~~~~~~~~~~~~~~~">
-        <location
-            file="src/android/support/v17/leanback/widget/GridLayoutManager.java"
-            line="1566"
-            column="37"/>
-    </issue>
-
-    <issue
-        id="WrongConstant"
-        message="Must be one of: View.VISIBLE, View.INVISIBLE, View.GONE"
-        errorLine1="                getChildAt(i).setVisibility(mChildVisibility);"
-        errorLine2="                                            ~~~~~~~~~~~~~~~~">
-        <location
-            file="src/android/support/v17/leanback/widget/GridLayoutManager.java"
-            line="3468"
-            column="45"/>
-    </issue>
-
-    <issue
-        id="WrongConstant"
-        message="Must be one of: ViewCompat.LAYOUT_DIRECTION_LTR, ViewCompat.LAYOUT_DIRECTION_RTL"
-        errorLine1="        boolean isRtl = ViewCompat.getLayoutDirection(view) == View.LAYOUT_DIRECTION_RTL;"
-        errorLine2="                                                               ~~~~~~~~~~~~~~~~~~~~~~~~~">
-        <location
-            file="src/android/support/v17/leanback/widget/HorizontalHoverCardSwitcher.java"
-            line="50"
-            column="64"/>
-    </issue>
-
-    <issue
-        id="WrongConstant"
-        message="Must be one of: ViewCompat.LAYOUT_DIRECTION_LTR, ViewCompat.LAYOUT_DIRECTION_RTL"
-        errorLine1="                    == View.LAYOUT_DIRECTION_RTL;"
-        errorLine2="                       ~~~~~~~~~~~~~~~~~~~~~~~~~">
-        <location
-            file="src/android/support/v17/leanback/widget/TitleHelper.java"
-            line="49"
-            column="24"/>
-    </issue>
-
-    <issue
-        id="RtlCompat"
-        message="Inconsistent alignment specification between `textAlignment` and `gravity` attributes: was `center_vertical|end`, expected `start`"
-        errorLine1="                    android:textAlignment=&quot;viewStart&quot;"
-        errorLine2="                                           ~~~~~~~~~">
-        <location
-            file="res/layout/lb_search_bar.xml"
-            line="70"
-            column="44"/>
-        <location
-            file="res/layout/lb_search_bar.xml"
-            line="57"
-            column="45"/>
+            line="1075"
+            column="47"/>
     </issue>
 
 </issues>
diff --git a/v17/leanback/res/animator/lb_decelerator_2.xml b/v17/leanback/res/anim/lb_decelerator_2.xml
similarity index 100%
rename from v17/leanback/res/animator/lb_decelerator_2.xml
rename to v17/leanback/res/anim/lb_decelerator_2.xml
diff --git a/v17/leanback/res/animator/lb_decelerator_4.xml b/v17/leanback/res/anim/lb_decelerator_4.xml
similarity index 100%
rename from v17/leanback/res/animator/lb_decelerator_4.xml
rename to v17/leanback/res/anim/lb_decelerator_4.xml
diff --git a/v17/leanback/res/animator-v21/lb_onboarding_description_enter.xml b/v17/leanback/res/animator-v21/lb_onboarding_description_enter.xml
new file mode 100644
index 0000000..3cb5843
--- /dev/null
+++ b/v17/leanback/res/animator-v21/lb_onboarding_description_enter.xml
@@ -0,0 +1,33 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  ~ Copyright (C) 2015 The Android Open Source Project
+  ~
+  ~ Licensed under the Apache License, Version 2.0 (the "License");
+  ~ you may not use this file except in compliance with the License.
+  ~ You may obtain a copy of the License at
+  ~
+  ~      http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing, software
+  ~ distributed under the License is distributed on an "AS IS" BASIS,
+  ~ WITHOUT 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="alpha"
+        android:valueFrom="0.0"
+        android:valueTo="1.0"
+        android:duration="533"
+        android:startOffset="@integer/lb_onboarding_header_description_delay"
+        android:interpolator="@android:interpolator/fast_out_slow_in" />
+    <objectAnimator
+        android:propertyName="translationY"
+        android:valueFrom="60dp"
+        android:valueTo="0dp"
+        android:duration="533"
+        android:startOffset="@integer/lb_onboarding_header_description_delay"
+        android:interpolator="@android:interpolator/fast_out_slow_in" />
+</set>
diff --git a/v17/leanback/res/animator-v21/lb_onboarding_logo_enter.xml b/v17/leanback/res/animator-v21/lb_onboarding_logo_enter.xml
new file mode 100644
index 0000000..76a4609
--- /dev/null
+++ b/v17/leanback/res/animator-v21/lb_onboarding_logo_enter.xml
@@ -0,0 +1,25 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  ~ Copyright (C) 2015 The Android Open Source Project
+  ~
+  ~ Licensed under the Apache License, Version 2.0 (the "License");
+  ~ you may not use this file except in compliance with the License.
+  ~ You may obtain a copy of the License at
+  ~
+  ~      http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing, software
+  ~ distributed under the License is distributed on an "AS IS" BASIS,
+  ~ WITHOUT 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="alpha"
+        android:valueFrom="0.0"
+        android:valueTo="1.0"
+        android:duration="333"
+        android:interpolator="@android:interpolator/linear_out_slow_in" />
+</set>
diff --git a/v17/leanback/res/animator-v21/lb_onboarding_logo_exit.xml b/v17/leanback/res/animator-v21/lb_onboarding_logo_exit.xml
new file mode 100644
index 0000000..40b618e
--- /dev/null
+++ b/v17/leanback/res/animator-v21/lb_onboarding_logo_exit.xml
@@ -0,0 +1,25 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  ~ Copyright (C) 2015 The Android Open Source Project
+  ~
+  ~ Licensed under the Apache License, Version 2.0 (the "License");
+  ~ you may not use this file except in compliance with the License.
+  ~ You may obtain a copy of the License at
+  ~
+  ~      http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing, software
+  ~ distributed under the License is distributed on an "AS IS" BASIS,
+  ~ WITHOUT 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="alpha"
+        android:valueFrom="1.0"
+        android:valueTo="0.0"
+        android:duration="666"
+        android:interpolator="@android:interpolator/fast_out_slow_in" />
+</set>
diff --git a/v17/leanback/res/animator-v21/lb_onboarding_page_indicator_enter.xml b/v17/leanback/res/animator-v21/lb_onboarding_page_indicator_enter.xml
new file mode 100644
index 0000000..e9fc46e
--- /dev/null
+++ b/v17/leanback/res/animator-v21/lb_onboarding_page_indicator_enter.xml
@@ -0,0 +1,25 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  ~ Copyright (C) 2015 The Android Open Source Project
+  ~
+  ~ Licensed under the Apache License, Version 2.0 (the "License");
+  ~ you may not use this file except in compliance with the License.
+  ~ You may obtain a copy of the License at
+  ~
+  ~      http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing, software
+  ~ distributed under the License is distributed on an "AS IS" BASIS,
+  ~ WITHOUT 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="alpha"
+        android:valueFrom="0.0"
+        android:valueTo="1.0"
+        android:duration="500"
+        android:interpolator="@android:interpolator/fast_out_slow_in" />
+</set>
diff --git a/v17/leanback/res/animator-v21/lb_onboarding_title_enter.xml b/v17/leanback/res/animator-v21/lb_onboarding_title_enter.xml
new file mode 100644
index 0000000..9b65b48
--- /dev/null
+++ b/v17/leanback/res/animator-v21/lb_onboarding_title_enter.xml
@@ -0,0 +1,33 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  ~ Copyright (C) 2015 The Android Open Source Project
+  ~
+  ~ Licensed under the Apache License, Version 2.0 (the "License");
+  ~ you may not use this file except in compliance with the License.
+  ~ You may obtain a copy of the License at
+  ~
+  ~      http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing, software
+  ~ distributed under the License is distributed on an "AS IS" BASIS,
+  ~ WITHOUT 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="alpha"
+        android:valueFrom="0.0"
+        android:valueTo="1.0"
+        android:duration="533"
+        android:startOffset="@integer/lb_onboarding_header_title_delay"
+        android:interpolator="@android:interpolator/fast_out_slow_in" />
+    <objectAnimator
+        android:propertyName="translationY"
+        android:valueFrom="60dp"
+        android:valueTo="0dp"
+        android:duration="533"
+        android:startOffset="@integer/lb_onboarding_header_title_delay"
+        android:interpolator="@android:interpolator/fast_out_slow_in" />
+</set>
diff --git a/v17/leanback/res/animator/lb_onboarding_description_enter.xml b/v17/leanback/res/animator/lb_onboarding_description_enter.xml
index 3cb5843..d8393bd 100644
--- a/v17/leanback/res/animator/lb_onboarding_description_enter.xml
+++ b/v17/leanback/res/animator/lb_onboarding_description_enter.xml
@@ -21,13 +21,11 @@
         android:valueFrom="0.0"
         android:valueTo="1.0"
         android:duration="533"
-        android:startOffset="@integer/lb_onboarding_header_description_delay"
-        android:interpolator="@android:interpolator/fast_out_slow_in" />
+        android:startOffset="@integer/lb_onboarding_header_description_delay" />
     <objectAnimator
         android:propertyName="translationY"
         android:valueFrom="60dp"
         android:valueTo="0dp"
         android:duration="533"
-        android:startOffset="@integer/lb_onboarding_header_description_delay"
-        android:interpolator="@android:interpolator/fast_out_slow_in" />
+        android:startOffset="@integer/lb_onboarding_header_description_delay" />
 </set>
diff --git a/v17/leanback/res/animator/lb_onboarding_logo_enter.xml b/v17/leanback/res/animator/lb_onboarding_logo_enter.xml
index 76a4609..5d8d762 100644
--- a/v17/leanback/res/animator/lb_onboarding_logo_enter.xml
+++ b/v17/leanback/res/animator/lb_onboarding_logo_enter.xml
@@ -20,6 +20,5 @@
         android:propertyName="alpha"
         android:valueFrom="0.0"
         android:valueTo="1.0"
-        android:duration="333"
-        android:interpolator="@android:interpolator/linear_out_slow_in" />
+        android:duration="333" />
 </set>
diff --git a/v17/leanback/res/animator/lb_onboarding_logo_exit.xml b/v17/leanback/res/animator/lb_onboarding_logo_exit.xml
index 40b618e..820ba8e 100644
--- a/v17/leanback/res/animator/lb_onboarding_logo_exit.xml
+++ b/v17/leanback/res/animator/lb_onboarding_logo_exit.xml
@@ -20,6 +20,5 @@
         android:propertyName="alpha"
         android:valueFrom="1.0"
         android:valueTo="0.0"
-        android:duration="666"
-        android:interpolator="@android:interpolator/fast_out_slow_in" />
+        android:duration="666" />
 </set>
diff --git a/v17/leanback/res/animator/lb_onboarding_page_indicator_enter.xml b/v17/leanback/res/animator/lb_onboarding_page_indicator_enter.xml
index e9fc46e..b8bd083 100644
--- a/v17/leanback/res/animator/lb_onboarding_page_indicator_enter.xml
+++ b/v17/leanback/res/animator/lb_onboarding_page_indicator_enter.xml
@@ -20,6 +20,5 @@
         android:propertyName="alpha"
         android:valueFrom="0.0"
         android:valueTo="1.0"
-        android:duration="500"
-        android:interpolator="@android:interpolator/fast_out_slow_in" />
+        android:duration="500" />
 </set>
diff --git a/v17/leanback/res/animator/lb_onboarding_title_enter.xml b/v17/leanback/res/animator/lb_onboarding_title_enter.xml
index 9b65b48..011f51c 100644
--- a/v17/leanback/res/animator/lb_onboarding_title_enter.xml
+++ b/v17/leanback/res/animator/lb_onboarding_title_enter.xml
@@ -21,13 +21,11 @@
         android:valueFrom="0.0"
         android:valueTo="1.0"
         android:duration="533"
-        android:startOffset="@integer/lb_onboarding_header_title_delay"
-        android:interpolator="@android:interpolator/fast_out_slow_in" />
+        android:startOffset="@integer/lb_onboarding_header_title_delay" />
     <objectAnimator
         android:propertyName="translationY"
         android:valueFrom="60dp"
         android:valueTo="0dp"
         android:duration="533"
-        android:startOffset="@integer/lb_onboarding_header_title_delay"
-        android:interpolator="@android:interpolator/fast_out_slow_in" />
+        android:startOffset="@integer/lb_onboarding_header_title_delay" />
 </set>
diff --git a/v17/leanback/res/layout/lb_playback_controls_row.xml b/v17/leanback/res/layout/lb_playback_controls_row.xml
index b449fa9..30d06bd 100644
--- a/v17/leanback/res/layout/lb_playback_controls_row.xml
+++ b/v17/leanback/res/layout/lb_playback_controls_row.xml
@@ -67,7 +67,7 @@
             <Space
                 android:id="@+id/spacer"
                 android:layout_width="match_parent"
-                android:layout_height="24dp" />
+                android:layout_height="16dp" />
 
             <FrameLayout
                 android:id="@+id/controls_dock"
diff --git a/v17/leanback/res/layout/lb_playback_transport_controls_row.xml b/v17/leanback/res/layout/lb_playback_transport_controls_row.xml
index 1b32be6..c8852e8 100644
--- a/v17/leanback/res/layout/lb_playback_transport_controls_row.xml
+++ b/v17/leanback/res/layout/lb_playback_transport_controls_row.xml
@@ -25,9 +25,7 @@
     android:orientation="vertical"
     android:clipChildren="false"
     android:clipToPadding="false"
-    android:paddingBottom="@dimen/lb_playback_transport_control_row_padding_bottom"
-    android:paddingStart="?attr/browsePaddingStart"
-    android:paddingEnd="?attr/browsePaddingEnd" >
+    android:paddingBottom="@dimen/lb_playback_transport_control_row_padding_bottom" >
 
     <FrameLayout
         android:layout_width="match_parent"
@@ -36,8 +34,10 @@
             android:id="@+id/controls_card"
             android:layout_width="match_parent"
             android:layout_height="wrap_content"
-            android:clipChildren="false"
+            android:layout_gravity="bottom"
             android:clipToPadding="false"
+            android:paddingStart="?attr/browsePaddingStart"
+            android:paddingEnd="?attr/browsePaddingEnd"
             android:layout_marginBottom="@dimen/lb_playback_transport_control_info_margin_bottom"
             android:orientation="horizontal" >
 
@@ -64,64 +64,75 @@
             android:visibility="invisible"
             android:layout_width="match_parent"
             android:layout_height="wrap_content"
-            android:layout_gravity="bottom" />
+            android:layout_gravity="bottom"
+            android:layout_marginBottom="@dimen/lb_playback_transport_thumbs_bottom_margin" />
     </FrameLayout>
 
-    <FrameLayout
-        android:id="@+id/controls_dock"
+    <LinearLayout
         android:layout_width="match_parent"
-        android:layout_height="wrap_content"
-        android:layoutDirection="ltr"
-        android:layout_marginLeft="@dimen/lb_playback_transport_controlbar_margin_start"
-    />
-
-    <android.support.v17.leanback.widget.SeekBar
-        android:id="@+id/playback_progress"
-        android:layout_width="match_parent"
-        android:layout_height="@dimen/lb_playback_transport_progressbar_height"
-        android:focusable="true" />
-
-    <RelativeLayout android:layout_width="match_parent"
-                    android:layout_height="wrap_content"
-                    android:layoutDirection="ltr"
-                    android:layout_marginLeft="@dimen/lb_playback_transport_controlbar_margin_start">
+        android:layout_height="match_parent"
+        android:orientation="vertical"
+        android:paddingStart="?attr/browsePaddingStart"
+        android:paddingEnd="?attr/browsePaddingEnd"
+        android:clipChildren="false"
+        android:clipToPadding="false">
         <FrameLayout
-            android:id="@+id/secondary_controls_dock"
-            android:layout_width="wrap_content"
+            android:id="@+id/controls_dock"
+            android:layout_width="match_parent"
             android:layout_height="wrap_content"
-            android:layout_alignParentStart="true" >
-        </FrameLayout>
+            android:layoutDirection="ltr"
+            android:layout_marginLeft="@dimen/lb_playback_transport_controlbar_margin_start"
+        />
 
-        <TextView
-            android:id="@+id/current_time"
-            android:layout_width="wrap_content"
-            android:layout_height="wrap_content"
-            android:layout_gravity="top"
-            android:layout_toStartOf="@+id/separate_time"
-            android:layout_marginStart="@dimen/lb_playback_transport_time_margin"
-            android:layout_marginTop="@dimen/lb_playback_transport_time_margin_top"
-            style="?attr/playbackControlsTimeStyle" />
+        <android.support.v17.leanback.widget.SeekBar
+            android:id="@+id/playback_progress"
+            android:layout_width="match_parent"
+            android:layout_height="@dimen/lb_playback_transport_progressbar_height"
+            android:focusable="true" />
 
-        <TextView
-            android:id="@+id/separate_time"
-            android:layout_width="wrap_content"
-            android:layout_height="wrap_content"
-            android:text="@string/lb_playback_time_separator"
-            android:layout_gravity="top"
-            android:layout_toStartOf="@+id/total_time"
-            android:layout_marginStart="@dimen/lb_playback_transport_time_margin"
-            android:layout_marginTop="@dimen/lb_playback_transport_time_margin_top"
-            style="?attr/playbackControlsTimeStyle" />
+        <RelativeLayout android:layout_width="match_parent"
+                        android:layout_height="wrap_content"
+                        android:layoutDirection="ltr"
+                        android:layout_marginLeft="@dimen/lb_playback_transport_controlbar_margin_start">
+            <FrameLayout
+                android:id="@+id/secondary_controls_dock"
+                android:layout_width="wrap_content"
+                android:layout_height="wrap_content"
+                android:layout_alignParentStart="true" >
+            </FrameLayout>
 
-        <TextView
-            android:id="@+id/total_time"
-            android:layout_width="wrap_content"
-            android:layout_height="wrap_content"
-            android:layout_gravity="top"
-            android:layout_alignParentEnd="true"
-            android:layout_marginStart="@dimen/lb_playback_transport_time_margin"
-            android:layout_marginTop="@dimen/lb_playback_transport_time_margin_top"
-            style="?attr/playbackControlsTimeStyle" />
-    </RelativeLayout>
+            <TextView
+                android:id="@+id/current_time"
+                android:layout_width="wrap_content"
+                android:layout_height="wrap_content"
+                android:layout_gravity="top"
+                android:layout_toStartOf="@+id/separate_time"
+                android:layout_marginStart="@dimen/lb_playback_transport_time_margin"
+                android:layout_marginTop="@dimen/lb_playback_transport_time_margin_top"
+                style="?attr/playbackControlsTimeStyle" />
 
+            <TextView
+                android:id="@+id/separate_time"
+                android:layout_width="wrap_content"
+                android:layout_height="wrap_content"
+                android:text="@string/lb_playback_time_separator"
+                android:layout_gravity="top"
+                android:layout_toStartOf="@+id/total_time"
+                android:layout_marginStart="@dimen/lb_playback_transport_time_margin"
+                android:layout_marginTop="@dimen/lb_playback_transport_time_margin_top"
+                style="?attr/playbackControlsTimeStyle" />
+
+            <TextView
+                android:id="@+id/total_time"
+                android:layout_width="wrap_content"
+                android:layout_height="wrap_content"
+                android:layout_gravity="top"
+                android:layout_alignParentEnd="true"
+                android:layout_marginStart="@dimen/lb_playback_transport_time_margin"
+                android:layout_marginTop="@dimen/lb_playback_transport_time_margin_top"
+                style="?attr/playbackControlsTimeStyle" />
+        </RelativeLayout>
+
+
+    </LinearLayout>
 </android.support.v17.leanback.widget.PlaybackTransportRowView>
diff --git a/v17/leanback/res/layout/lb_row_media_item.xml b/v17/leanback/res/layout/lb_row_media_item.xml
index b25e922..e76e281 100644
--- a/v17/leanback/res/layout/lb_row_media_item.xml
+++ b/v17/leanback/res/layout/lb_row_media_item.xml
@@ -62,6 +62,7 @@
 
             <LinearLayout
                     android:id="@+id/mediaItemActionsContainer"
+                    android:orientation="horizontal"
                     android:layout_width="wrap_content"
                     android:layout_height="match_parent"
                     android:paddingStart="16dip" />
diff --git a/v17/leanback/res/layout/lb_search_bar.xml b/v17/leanback/res/layout/lb_search_bar.xml
index 37cdfb3..73a5985 100644
--- a/v17/leanback/res/layout/lb_search_bar.xml
+++ b/v17/leanback/res/layout/lb_search_bar.xml
@@ -54,7 +54,7 @@
                     android:id="@+id/lb_search_text_editor"
                     android:layout_width="match_parent"
                     android:layout_height="wrap_content"
-                    android:layout_gravity="center_vertical|end"
+                    android:layout_gravity="center_vertical|start"
                     android:layout_marginStart="@dimen/lb_search_bar_edit_text_margin_start"
                     android:layout_centerVertical="true"
                     android:cursorVisible="true"
diff --git a/v17/leanback/res/transition-v21/lb_title_out.xml b/v17/leanback/res/transition-v21/lb_title_out.xml
index 69735ab..2402857 100644
--- a/v17/leanback/res/transition-v21/lb_title_out.xml
+++ b/v17/leanback/res/transition-v21/lb_title_out.xml
@@ -18,7 +18,7 @@
     xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:lb="http://schemas.android.com/apk/res-auto"
     class="android.support.v17.leanback.transition.SlideKitkat"
-    android:interpolator="@animator/lb_decelerator_2"
+    android:interpolator="@anim/lb_decelerator_2"
     lb:lb_slideEdge="top" >
     <targets>
         <target android:targetId="@id/browse_title_group" />
diff --git a/v17/leanback/res/values-v18/themes.xml b/v17/leanback/res/values-v18/themes.xml
new file mode 100644
index 0000000..9fc7722
--- /dev/null
+++ b/v17/leanback/res/values-v18/themes.xml
@@ -0,0 +1,28 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+     Copyright (C) 2014 The Android Open Source Project
+
+     Licensed under the Apache License, Version 2.0 (the "License");
+     you may not use this file except in compliance with the License.
+     You may obtain a copy of the License at
+
+          http://www.apache.org/licenses/LICENSE-2.0
+
+     Unless required by applicable law or agreed to in writing, software
+     distributed under the License is distributed on an "AS IS" BASIS,
+     WITHOUT 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="Theme.LeanbackBase" parent="android:Theme.Holo.NoActionBar">
+        <item name="playbackProgressPrimaryColor">@color/lb_playback_progress_color_no_theme</item>
+        <item name="playbackControlsIconHighlightColor">@color/lb_playback_icon_highlight_no_theme</item>
+        <item name="defaultBrandColor">@color/lb_default_brand_color</item>
+        <item name="defaultBrandColorDark">@color/lb_default_brand_color_dark</item>
+
+        <item name="android:windowOverscan">true</item>
+        <item name="guidedStepTheme">@style/Theme.Leanback.GuidedStep</item>
+    </style>
+</resources>
diff --git a/v17/leanback/res/values-v21/styles.xml b/v17/leanback/res/values-v21/styles.xml
index 89029c4..cd81934 100644
--- a/v17/leanback/res/values-v21/styles.xml
+++ b/v17/leanback/res/values-v21/styles.xml
@@ -25,4 +25,9 @@
         <item name="android:background">@drawable/lb_action_bg</item>
     </style>
 
+    <style name="Widget.Leanback.OnboardingStartButtonStyleBase">
+        <item name="android:elevation">1.5dp</item>
+        <item name="android:stateListAnimator">@null</item>
+    </style>
+
 </resources>
\ No newline at end of file
diff --git a/v17/leanback/res/values-v21/themes.xml b/v17/leanback/res/values-v21/themes.xml
index 886077a..4e5bd5a 100644
--- a/v17/leanback/res/values-v21/themes.xml
+++ b/v17/leanback/res/values-v21/themes.xml
@@ -26,10 +26,49 @@
 
         <item name="android:windowOverscan">true</item>
         <item name="guidedStepTheme">@style/Theme.Leanback.GuidedStep</item>
+
+        <!-- android:windowSharedElementEnterTransition is kept for backward compatibility for apps still refer
+        to Theme.Leanback, app should use Theme.Leanback.Details instead -->
+        <item name="android:windowSharedElementEnterTransition">@transition/lb_shared_element_enter_transition</item>
+        <!-- android:windowSharedElementReturnTransition is kept for backward compatibility for apps still refer
+        to Theme.Leanback, app should use Theme.Leanback.Details instead -->
+        <item name="android:windowSharedElementReturnTransition">@transition/lb_shared_element_return_transition</item>
+        <item name="android:windowEnterTransition">@transition/lb_enter_transition</item>
+        <item name="android:windowReturnTransition">@transition/lb_return_transition</item>
+        <item name="android:windowTransitionBackgroundFadeDuration">350</item>
+
+    </style>
+
+    <style name="Theme.Leanback.Browse" parent="Theme.Leanback">
+        <item name="android:windowEnterTransition">@transition/lb_browse_enter_transition</item>
+        <item name="android:windowReturnTransition">@transition/lb_browse_return_transition</item>
+    </style>
+
+    <style name="Theme.Leanback.VerticalGrid" parent="Theme.Leanback">
+        <item name="android:windowEnterTransition">@transition/lb_vertical_grid_enter_transition</item>
+        <item name="android:windowReturnTransition">@transition/lb_vertical_grid_return_transition</item>
+    </style>
+
+    <style name="Theme.Leanback.Details" parent="Theme.Leanback">
+        <item name="android:windowEnterTransition">@transition/lb_details_enter_transition</item>
+        <item name="android:windowReturnTransition">@transition/lb_details_return_transition</item>
+        <item name="android:windowSharedElementEnterTransition">@transition/lb_shared_element_enter_transition</item>
+        <item name="android:windowSharedElementReturnTransition">@transition/lb_shared_element_return_transition</item>
+    </style>
+
+    <style name="Theme.Leanback.Details.NoSharedElementTransition">
+        <item name="android:windowSharedElementEnterTransition">@null</item>
+        <item name="android:windowSharedElementReturnTransition">@null</item>
     </style>
 
     <style name="Theme.Leanback.GuidedStepBase" parent="Theme.LeanbackBase">
         <item name="guidedActionsSelectorDrawable">@drawable/lb_selectable_item_rounded_rect</item>
+        <item name="android:windowEnterTransition">@transition/lb_guidedstep_activity_enter</item>
+        <item name="android:windowTransitionBackgroundFadeDuration">@integer/lb_guidedstep_activity_background_fade_duration_ms</item>
+    </style>
+
+    <style name="Theme.Leanback.GuidedStep.HalfBase" parent="Theme.Leanback.GuidedStep">
+        <item name="android:windowEnterTransition">@transition/lb_guidedstep_activity_enter_bottom</item>
     </style>
 
 </resources>
diff --git a/v17/leanback/res/values/dimens.xml b/v17/leanback/res/values/dimens.xml
index 0a2b485..7a39b78 100644
--- a/v17/leanback/res/values/dimens.xml
+++ b/v17/leanback/res/values/dimens.xml
@@ -176,6 +176,14 @@
     <!-- radius of thumb when focused -->
     <dimen name="lb_playback_transport_progressbar_active_radius">6dp</dimen>
 
+    <!-- Thumbs bar -->
+    <dimen name="lb_playback_transport_thumbs_width">154dp</dimen>
+    <dimen name="lb_playback_transport_thumbs_height">154dp</dimen>
+    <dimen name="lb_playback_transport_hero_thumbs_width">192dp</dimen>
+    <dimen name="lb_playback_transport_hero_thumbs_height">192dp</dimen>
+    <dimen name="lb_playback_transport_thumbs_margin">4dp</dimen>
+    <dimen name="lb_playback_transport_thumbs_bottom_margin">18dp</dimen>
+
     <dimen name="lb_playback_transport_time_margin">8dp</dimen>
     <dimen name="lb_playback_transport_time_margin_top">8dp</dimen>
 
diff --git a/v17/leanback/res/values/styles.xml b/v17/leanback/res/values/styles.xml
index d10260d..b7e3da2 100644
--- a/v17/leanback/res/values/styles.xml
+++ b/v17/leanback/res/values/styles.xml
@@ -751,19 +751,20 @@
         <item name="arrowBgColor">@color/lb_page_indicator_arrow_background</item>
     </style>
 
+    <style name="Widget.Leanback.OnboardingStartButtonStyleBase">
+    </style>
+
     <!-- Style for the start button in OnboardingFragment. -->
-    <style name="Widget.Leanback.OnboardingStartButtonStyle">
+    <style name="Widget.Leanback.OnboardingStartButtonStyle" parent="Widget.Leanback.OnboardingStartButtonStyleBase">
         <item name="android:layout_width">wrap_content</item>
         <item name="android:layout_height">36dp</item>
         <item name="android:layout_gravity">center_horizontal</item>
         <item name="android:layout_marginBottom">4dp</item>
         <item name="android:background">@drawable/lb_onboarding_start_button_background</item>
-        <item name="android:elevation">1.5dp</item>
         <item name="android:fontFamily">sans-serif</item>
         <item name="android:gravity">center_vertical</item>
         <item name="android:paddingEnd">24dp</item>
         <item name="android:paddingStart">24dp</item>
-        <item name="android:stateListAnimator">@null</item>
         <item name="android:text">@string/lb_onboarding_get_started</item>
         <item name="android:textAllCaps">true</item>
         <item name="android:textColor">#014269</item>
diff --git a/v17/leanback/res/values/themes.xml b/v17/leanback/res/values/themes.xml
index e5b98f6..d40734e 100644
--- a/v17/leanback/res/values/themes.xml
+++ b/v17/leanback/res/values/themes.xml
@@ -24,7 +24,6 @@
         <item name="defaultBrandColor">@color/lb_default_brand_color</item>
         <item name="defaultBrandColorDark">@color/lb_default_brand_color_dark</item>
 
-        <item name="android:windowOverscan">true</item>
         <item name="guidedStepTheme">@style/Theme.Leanback.GuidedStep</item>
     </style>
 
@@ -103,16 +102,6 @@
 
         <item name="defaultSectionHeaderColor">?attr/defaultSearchColor</item>
 
-        <!-- android:windowSharedElementEnterTransition is kept for backward compatibility for apps still refer
-        to Theme.Leanback, app should use Theme.Leanback.Details instead -->
-        <item name="android:windowSharedElementEnterTransition">@transition/lb_shared_element_enter_transition</item>
-        <!-- android:windowSharedElementReturnTransition is kept for backward compatibility for apps still refer
-        to Theme.Leanback, app should use Theme.Leanback.Details instead -->
-        <item name="android:windowSharedElementReturnTransition">@transition/lb_shared_element_return_transition</item>
-        <item name="android:windowEnterTransition">@transition/lb_enter_transition</item>
-        <item name="android:windowReturnTransition">@transition/lb_return_transition</item>
-        <item name="android:windowTransitionBackgroundFadeDuration">350</item>
-
         <item name="overlayDimMaskColor">@color/lb_view_dim_mask_color</item>
         <item name="overlayDimActiveLevel">@fraction/lb_view_active_level</item>
         <item name="overlayDimDimmedLevel">@fraction/lb_view_dimmed_level</item>
@@ -120,26 +109,16 @@
     </style>
 
     <style name="Theme.Leanback.Browse" parent="Theme.Leanback">
-        <item name="android:windowEnterTransition">@transition/lb_browse_enter_transition</item>
-        <item name="android:windowReturnTransition">@transition/lb_browse_return_transition</item>
     </style>
 
     <style name="Theme.Leanback.VerticalGrid" parent="Theme.Leanback">
-        <item name="android:windowEnterTransition">@transition/lb_vertical_grid_enter_transition</item>
-        <item name="android:windowReturnTransition">@transition/lb_vertical_grid_return_transition</item>
     </style>
 
     <style name="Theme.Leanback.Details" parent="Theme.Leanback">
-        <item name="android:windowEnterTransition">@transition/lb_details_enter_transition</item>
-        <item name="android:windowReturnTransition">@transition/lb_details_return_transition</item>
-        <item name="android:windowSharedElementEnterTransition">@transition/lb_shared_element_enter_transition</item>
-        <item name="android:windowSharedElementReturnTransition">@transition/lb_shared_element_return_transition</item>
     </style>
 
     <!-- Theme for the details without shared element transition -->
     <style name="Theme.Leanback.Details.NoSharedElementTransition">
-        <item name="android:windowSharedElementEnterTransition">@null</item>
-        <item name="android:windowSharedElementReturnTransition">@null</item>
     </style>
 
     <style name="Theme.Leanback.GuidedStepBase" parent="Theme.LeanbackBase">
@@ -150,7 +129,6 @@
         <item name="guidedStepThemeFlag">true</item>
         <item name="guidedStepHeightWeight">@string/lb_guidedstep_height_weight</item>
 
-        <item name="android:windowEnterTransition">@transition/lb_guidedstep_activity_enter</item>
 
         <!-- background applied to each GuidedStepFragment by default-->
         <item name="guidedStepBackground">?android:attr/colorBackground</item>
@@ -158,7 +136,6 @@
              But We still need a dumb background to keep the temporary translucent state last
              as long as the background view fade-in transition -->
         <item name="android:windowBackground">@android:color/transparent</item>
-        <item name="android:windowTransitionBackgroundFadeDuration">@integer/lb_guidedstep_activity_background_fade_duration_ms</item>
 
         <item name="guidedStepImeAppearingAnimation">@animator/lb_guidedstep_slide_up</item>
         <item name="guidedStepImeDisappearingAnimation">@animator/lb_guidedstep_slide_down</item>
@@ -198,8 +175,10 @@
         <item name="guidedStepKeyline">@string/lb_guidedstep_keyline</item>
     </style>
 
-    <style name="Theme.Leanback.GuidedStep.Half" parent="Theme.Leanback.GuidedStep">
-      <item name="android:windowEnterTransition">@transition/lb_guidedstep_activity_enter_bottom</item>
+    <style name="Theme.Leanback.GuidedStep.HalfBase" parent="Theme.Leanback.GuidedStep">
+    </style>
+
+    <style name="Theme.Leanback.GuidedStep.Half" parent="Theme.Leanback.GuidedStep.HalfBase">
       <item name="guidedStepHeightWeight">@string/lb_guidedstep_height_weight_translucent</item>
       <item name="android:windowIsTranslucent">true</item>
       <item name="android:windowBackground">@android:color/transparent</item>
diff --git a/v17/leanback/src/android/support/v17/leanback/app/BrowseFragment.java b/v17/leanback/src/android/support/v17/leanback/app/BrowseFragment.java
index 698b5f2..fd930ee 100644
--- a/v17/leanback/src/android/support/v17/leanback/app/BrowseFragment.java
+++ b/v17/leanback/src/android/support/v17/leanback/app/BrowseFragment.java
@@ -1026,7 +1026,8 @@
                         ? mHeadersFragment.getVerticalGridView() : mMainFragment.getView();
             }
 
-            boolean isRtl = ViewCompat.getLayoutDirection(focused) == View.LAYOUT_DIRECTION_RTL;
+            boolean isRtl = ViewCompat.getLayoutDirection(focused)
+                    == ViewCompat.LAYOUT_DIRECTION_RTL;
             int towardStart = isRtl ? View.FOCUS_RIGHT : View.FOCUS_LEFT;
             int towardEnd = isRtl ? View.FOCUS_LEFT : View.FOCUS_RIGHT;
             if (mCanShowHeaders && direction == towardStart) {
diff --git a/v17/leanback/src/android/support/v17/leanback/app/BrowseSupportFragment.java b/v17/leanback/src/android/support/v17/leanback/app/BrowseSupportFragment.java
index 34c6775..094fef9 100644
--- a/v17/leanback/src/android/support/v17/leanback/app/BrowseSupportFragment.java
+++ b/v17/leanback/src/android/support/v17/leanback/app/BrowseSupportFragment.java
@@ -1029,7 +1029,8 @@
                         ? mHeadersSupportFragment.getVerticalGridView() : mMainFragment.getView();
             }
 
-            boolean isRtl = ViewCompat.getLayoutDirection(focused) == View.LAYOUT_DIRECTION_RTL;
+            boolean isRtl = ViewCompat.getLayoutDirection(focused)
+                    == ViewCompat.LAYOUT_DIRECTION_RTL;
             int towardStart = isRtl ? View.FOCUS_RIGHT : View.FOCUS_LEFT;
             int towardEnd = isRtl ? View.FOCUS_LEFT : View.FOCUS_RIGHT;
             if (mCanShowHeaders && direction == towardStart) {
diff --git a/v17/leanback/src/android/support/v17/leanback/app/GuidedStepFragment.java b/v17/leanback/src/android/support/v17/leanback/app/GuidedStepFragment.java
index 33ec805..a01cf26 100644
--- a/v17/leanback/src/android/support/v17/leanback/app/GuidedStepFragment.java
+++ b/v17/leanback/src/android/support/v17/leanback/app/GuidedStepFragment.java
@@ -234,7 +234,7 @@
     @RestrictTo(LIBRARY_GROUP)
     public static final int SLIDE_FROM_BOTTOM = 1;
 
-    private static final String TAG = "GuidedStepFragment";
+    private static final String TAG = "GuidedStepF";
     private static final boolean DEBUG = false;
 
     /**
diff --git a/v17/leanback/src/android/support/v17/leanback/app/GuidedStepSupportFragment.java b/v17/leanback/src/android/support/v17/leanback/app/GuidedStepSupportFragment.java
index 9bd9b08..ed34548 100644
--- a/v17/leanback/src/android/support/v17/leanback/app/GuidedStepSupportFragment.java
+++ b/v17/leanback/src/android/support/v17/leanback/app/GuidedStepSupportFragment.java
@@ -237,7 +237,7 @@
     @RestrictTo(LIBRARY_GROUP)
     public static final int SLIDE_FROM_BOTTOM = 1;
 
-    private static final String TAG = "GuidedStepSupportFragment";
+    private static final String TAG = "GuidedStepF";
     private static final boolean DEBUG = false;
 
     /**
diff --git a/v17/leanback/src/android/support/v17/leanback/app/OnboardingFragment.java b/v17/leanback/src/android/support/v17/leanback/app/OnboardingFragment.java
index ab61c4a..5eb2784 100644
--- a/v17/leanback/src/android/support/v17/leanback/app/OnboardingFragment.java
+++ b/v17/leanback/src/android/support/v17/leanback/app/OnboardingFragment.java
@@ -152,7 +152,7 @@
  * @attr ref R.styleable#LeanbackOnboardingTheme_onboardingLogoStyle
  */
 abstract public class OnboardingFragment extends Fragment {
-    private static final String TAG = "OnboardingFragment";
+    private static final String TAG = "OnboardingF";
     private static final boolean DEBUG = false;
 
     private static final long LOGO_SPLASH_PAUSE_DURATION_MS = 1333;
diff --git a/v17/leanback/src/android/support/v17/leanback/app/OnboardingSupportFragment.java b/v17/leanback/src/android/support/v17/leanback/app/OnboardingSupportFragment.java
index 0c7cc4c..46c2a81 100644
--- a/v17/leanback/src/android/support/v17/leanback/app/OnboardingSupportFragment.java
+++ b/v17/leanback/src/android/support/v17/leanback/app/OnboardingSupportFragment.java
@@ -155,7 +155,7 @@
  * @attr ref R.styleable#LeanbackOnboardingTheme_onboardingLogoStyle
  */
 abstract public class OnboardingSupportFragment extends Fragment {
-    private static final String TAG = "OnboardingSupportFragment";
+    private static final String TAG = "OnboardingF";
     private static final boolean DEBUG = false;
 
     private static final long LOGO_SPLASH_PAUSE_DURATION_MS = 1333;
diff --git a/v17/leanback/src/android/support/v17/leanback/app/PlaybackOverlayFragment.java b/v17/leanback/src/android/support/v17/leanback/app/PlaybackOverlayFragment.java
index c934f6b..d4b532b 100644
--- a/v17/leanback/src/android/support/v17/leanback/app/PlaybackOverlayFragment.java
+++ b/v17/leanback/src/android/support/v17/leanback/app/PlaybackOverlayFragment.java
@@ -96,7 +96,7 @@
         }
     }
 
-    static final String TAG = "PlaybackOverlayFragment";
+    static final String TAG = "PlaybackOF";
     static final boolean DEBUG = false;
     private static final int ANIMATION_MULTIPLIER = 1;
 
diff --git a/v17/leanback/src/android/support/v17/leanback/app/PlaybackOverlaySupportFragment.java b/v17/leanback/src/android/support/v17/leanback/app/PlaybackOverlaySupportFragment.java
index ecabdaa..d751320 100644
--- a/v17/leanback/src/android/support/v17/leanback/app/PlaybackOverlaySupportFragment.java
+++ b/v17/leanback/src/android/support/v17/leanback/app/PlaybackOverlaySupportFragment.java
@@ -99,7 +99,7 @@
         }
     }
 
-    static final String TAG = "PlaybackOverlaySupportFragment";
+    static final String TAG = "PlaybackOF";
     static final boolean DEBUG = false;
     private static final int ANIMATION_MULTIPLIER = 1;
 
diff --git a/v17/leanback/src/android/support/v17/leanback/app/VerticalGridFragment.java b/v17/leanback/src/android/support/v17/leanback/app/VerticalGridFragment.java
index 9e80dfc..5cf5799 100644
--- a/v17/leanback/src/android/support/v17/leanback/app/VerticalGridFragment.java
+++ b/v17/leanback/src/android/support/v17/leanback/app/VerticalGridFragment.java
@@ -38,7 +38,7 @@
  * an {@link ObjectAdapter}.
  */
 public class VerticalGridFragment extends BaseFragment {
-    static final String TAG = "VerticalGridFragment";
+    static final String TAG = "VerticalGF";
     static boolean DEBUG = false;
 
     private ObjectAdapter mAdapter;
diff --git a/v17/leanback/src/android/support/v17/leanback/app/VerticalGridSupportFragment.java b/v17/leanback/src/android/support/v17/leanback/app/VerticalGridSupportFragment.java
index 6327790..a38bac5 100644
--- a/v17/leanback/src/android/support/v17/leanback/app/VerticalGridSupportFragment.java
+++ b/v17/leanback/src/android/support/v17/leanback/app/VerticalGridSupportFragment.java
@@ -41,7 +41,7 @@
  * an {@link ObjectAdapter}.
  */
 public class VerticalGridSupportFragment extends BaseSupportFragment {
-    static final String TAG = "VerticalGridSupportFragment";
+    static final String TAG = "VerticalGF";
     static boolean DEBUG = false;
 
     private ObjectAdapter mAdapter;
diff --git a/v17/leanback/src/android/support/v17/leanback/transition/LeanbackTransitionHelper.java b/v17/leanback/src/android/support/v17/leanback/transition/LeanbackTransitionHelper.java
index 2598969..f97d64e 100644
--- a/v17/leanback/src/android/support/v17/leanback/transition/LeanbackTransitionHelper.java
+++ b/v17/leanback/src/android/support/v17/leanback/transition/LeanbackTransitionHelper.java
@@ -17,6 +17,7 @@
 
 import android.content.Context;
 import android.os.Build;
+import android.support.annotation.RequiresApi;
 import android.support.annotation.RestrictTo;
 import android.support.v17.leanback.R;
 
@@ -36,6 +37,7 @@
      * Kitkat does not allow load custom transition from resource, calling
      * LeanbackTransitionHelperKitKat to build custom transition in code.
      */
+    @RequiresApi(19)
     static class LeanbackTransitionHelperKitKatImpl implements LeanbackTransitionHelperVersion {
 
         @Override
diff --git a/v17/leanback/src/android/support/v17/leanback/transition/TransitionHelper.java b/v17/leanback/src/android/support/v17/leanback/transition/TransitionHelper.java
index 9f92066..de8b374 100644
--- a/v17/leanback/src/android/support/v17/leanback/transition/TransitionHelper.java
+++ b/v17/leanback/src/android/support/v17/leanback/transition/TransitionHelper.java
@@ -17,6 +17,7 @@
 
 import android.content.Context;
 import android.os.Build;
+import android.support.annotation.RequiresApi;
 import android.support.annotation.RestrictTo;
 import android.view.Gravity;
 import android.view.View;
@@ -427,6 +428,7 @@
     /**
      * Implementation used on KitKat (and above).
      */
+    @RequiresApi(19)
     static class TransitionHelperKitkatImpl extends TransitionHelperStubImpl {
 
         @Override
@@ -566,6 +568,7 @@
         }
     }
 
+    @RequiresApi(21)
     static final class TransitionHelperApi21Impl extends TransitionHelperKitkatImpl {
 
         @Override
diff --git a/v17/leanback/src/android/support/v17/leanback/widget/BackgroundHelper.java b/v17/leanback/src/android/support/v17/leanback/widget/BackgroundHelper.java
index 62d94d2..6bc2462 100644
--- a/v17/leanback/src/android/support/v17/leanback/widget/BackgroundHelper.java
+++ b/v17/leanback/src/android/support/v17/leanback/widget/BackgroundHelper.java
@@ -19,6 +19,7 @@
 
 import android.graphics.drawable.Drawable;
 import android.os.Build;
+import android.support.annotation.RequiresApi;
 import android.support.annotation.RestrictTo;
 import android.view.View;
 
@@ -47,6 +48,7 @@
         }
     }
 
+    @RequiresApi(19)
     private static final class BackgroundHelperKitkatImpl implements BackgroundHelperVersionImpl {
         BackgroundHelperKitkatImpl() {
         }
diff --git a/v17/leanback/src/android/support/v17/leanback/widget/BaseCardView.java b/v17/leanback/src/android/support/v17/leanback/widget/BaseCardView.java
index 9ca331f..735bb99 100644
--- a/v17/leanback/src/android/support/v17/leanback/widget/BaseCardView.java
+++ b/v17/leanback/src/android/support/v17/leanback/widget/BaseCardView.java
@@ -891,7 +891,7 @@
          * @param source The layout params to copy from.
          */
         public LayoutParams(LayoutParams source) {
-            super(source);
+            super((ViewGroup.MarginLayoutParams) source);
 
             this.viewType = source.viewType;
         }
diff --git a/v17/leanback/src/android/support/v17/leanback/widget/DetailsOverviewRowPresenter.java b/v17/leanback/src/android/support/v17/leanback/widget/DetailsOverviewRowPresenter.java
index d484bcc..98b9f78 100644
--- a/v17/leanback/src/android/support/v17/leanback/widget/DetailsOverviewRowPresenter.java
+++ b/v17/leanback/src/android/support/v17/leanback/widget/DetailsOverviewRowPresenter.java
@@ -61,7 +61,7 @@
 @Deprecated
 public class DetailsOverviewRowPresenter extends RowPresenter {
 
-    static final String TAG = "DetailsOverviewRowPresenter";
+    static final String TAG = "DetailsOverviewRowP";
     static final boolean DEBUG = false;
 
     private static final int MORE_ACTIONS_FADE_MS = 100;
diff --git a/v17/leanback/src/android/support/v17/leanback/widget/DetailsOverviewSharedElementHelper.java b/v17/leanback/src/android/support/v17/leanback/widget/DetailsOverviewSharedElementHelper.java
index 50f0da3..0d86cfd 100644
--- a/v17/leanback/src/android/support/v17/leanback/widget/DetailsOverviewSharedElementHelper.java
+++ b/v17/leanback/src/android/support/v17/leanback/widget/DetailsOverviewSharedElementHelper.java
@@ -177,6 +177,9 @@
         mViewHolder.mActionsRow.setDescendantFocusability(ViewGroup.FOCUS_BEFORE_DESCENDANTS);
         mViewHolder.mActionsRow.setVisibility(View.VISIBLE);
         mViewHolder.mActionsRow.setDescendantFocusability(ViewGroup.FOCUS_AFTER_DESCENDANTS);
+        // switch focusability to VISIBLE wont trigger focusableViewAvailable() on O because
+        // shared element details_frame is still INVISIBLE. b/63544781
+        mViewHolder.mActionsRow.requestFocus();
         mViewHolder.mDetailsDescriptionFrame.setVisibility(View.VISIBLE);
     }
 
diff --git a/v17/leanback/src/android/support/v17/leanback/widget/FocusHighlightHelper.java b/v17/leanback/src/android/support/v17/leanback/widget/FocusHighlightHelper.java
index 394142d..49565ab 100644
--- a/v17/leanback/src/android/support/v17/leanback/widget/FocusHighlightHelper.java
+++ b/v17/leanback/src/android/support/v17/leanback/widget/FocusHighlightHelper.java
@@ -25,6 +25,7 @@
 import android.support.v17.leanback.app.HeadersFragment;
 import android.support.v17.leanback.graphics.ColorOverlayDimmer;
 import android.support.v7.widget.RecyclerView;
+import android.util.TypedValue;
 import android.view.View;
 import android.view.ViewParent;
 import android.view.animation.AccelerateDecelerateInterpolator;
@@ -272,11 +273,15 @@
         void lazyInit(View view) {
             if (!mInitialized) {
                 Resources res = view.getResources();
-                mSelectScale = mScaleEnabled
-                        ? Float.parseFloat(res.getString(R.dimen.lb_browse_header_select_scale))
-                        : 1f;
-                mDuration =
-                        Integer.parseInt(res.getString(R.dimen.lb_browse_header_select_duration));
+                TypedValue value = new TypedValue();
+                if (mScaleEnabled) {
+                    res.getValue(R.dimen.lb_browse_header_select_scale, value, true);
+                    mSelectScale = value.getFloat();
+                } else {
+                    mSelectScale = 1f;
+                }
+                res.getValue(R.dimen.lb_browse_header_select_duration, value, true);
+                mDuration = value.data;
                 mInitialized = true;
             }
         }
diff --git a/v17/leanback/src/android/support/v17/leanback/widget/ForegroundHelper.java b/v17/leanback/src/android/support/v17/leanback/widget/ForegroundHelper.java
index 64cb769..4c2a857 100644
--- a/v17/leanback/src/android/support/v17/leanback/widget/ForegroundHelper.java
+++ b/v17/leanback/src/android/support/v17/leanback/widget/ForegroundHelper.java
@@ -2,6 +2,7 @@
 
 import android.graphics.drawable.Drawable;
 import android.os.Build;
+import android.support.annotation.RequiresApi;
 import android.view.View;
 
 final class ForegroundHelper {
@@ -22,6 +23,7 @@
     /**
      * Implementation used on api 23 (and above).
      */
+    @RequiresApi(23)
     private static final class ForegroundHelperApi23Impl implements ForegroundHelperVersionImpl {
         ForegroundHelperApi23Impl() {
         }
diff --git a/v17/leanback/src/android/support/v17/leanback/widget/FullWidthDetailsOverviewRowPresenter.java b/v17/leanback/src/android/support/v17/leanback/widget/FullWidthDetailsOverviewRowPresenter.java
index 189dfe6..dad4414 100644
--- a/v17/leanback/src/android/support/v17/leanback/widget/FullWidthDetailsOverviewRowPresenter.java
+++ b/v17/leanback/src/android/support/v17/leanback/widget/FullWidthDetailsOverviewRowPresenter.java
@@ -62,7 +62,7 @@
  */
 public class FullWidthDetailsOverviewRowPresenter extends RowPresenter {
 
-    static final String TAG = "FullWidthDetailsOverviewRowPresenter";
+    static final String TAG = "FullWidthDetailsRP";
     static final boolean DEBUG = false;
 
     private static Rect sTmpRect = new Rect();
diff --git a/v17/leanback/src/android/support/v17/leanback/widget/GridLayoutManager.java b/v17/leanback/src/android/support/v17/leanback/widget/GridLayoutManager.java
index d4e694a..45d69ef 100644
--- a/v17/leanback/src/android/support/v17/leanback/widget/GridLayoutManager.java
+++ b/v17/leanback/src/android/support/v17/leanback/widget/GridLayoutManager.java
@@ -504,7 +504,8 @@
     /**
      * override child visibility
      */
-    int mChildVisibility = -1;
+    @Visibility
+    int mChildVisibility;
 
     /**
      * Pixels that scrolled in secondary forward direction. Negative value means backward.
@@ -668,6 +669,7 @@
 
     public GridLayoutManager(BaseGridView baseGridView) {
         mBaseGridView = baseGridView;
+        mChildVisibility = -1;
     }
 
     public void setOrientation(int orientation) {
@@ -3188,9 +3190,13 @@
             final View focused = recyclerView.findFocus();
             final int focusedIndex = findImmediateChildIndex(focused);
             final int focusedPos = getAdapterPositionByIndex(focusedIndex);
+            // Even if focusedPos != NO_POSITION, findViewByPosition could return null if the view
+            // is ignored or getLayoutPosition does not match the adapter position of focused view.
+            final View immediateFocusedChild = (focusedPos == NO_POSITION) ? null
+                    : findViewByPosition(focusedPos);
             // Add focusables of focused item.
-            if (focusedPos != NO_POSITION) {
-                findViewByPosition(focusedPos).addFocusables(views,  direction, focusableMode);
+            if (immediateFocusedChild != null) {
+                immediateFocusedChild.addFocusables(views,  direction, focusableMode);
             }
             if (mGrid == null || getChildCount() == 0) {
                 // no grid information, or no child, bail out.
@@ -3201,7 +3207,7 @@
                 return true;
             }
             // Add focusables of neighbor depending on the focus search direction.
-            final int focusedRow = mGrid != null && focusedPos != NO_POSITION
+            final int focusedRow = mGrid != null && immediateFocusedChild != null
                     ? mGrid.getLocation(focusedPos).row : NO_POSITION;
             final int focusableCount = views.size();
             int inc = movement == NEXT_ITEM || movement == NEXT_ROW ? 1 : -1;
@@ -3217,9 +3223,9 @@
                 if (child.getVisibility() != View.VISIBLE || !child.hasFocusable()) {
                     continue;
                 }
-                // if there wasn't any focusing item,  add the very first focusable
+                // if there wasn't any focused item, add the very first focusable
                 // items and stop.
-                if (focusedPos == NO_POSITION) {
+                if (immediateFocusedChild == null) {
                     child.addFocusables(views,  direction, focusableMode);
                     if (views.size() > focusableCount) {
                         break;
@@ -3315,6 +3321,15 @@
         return count == 0 || mBaseGridView.findViewHolderForAdapterPosition(0) != null;
     }
 
+    boolean isItemFullyVisible(int pos) {
+        RecyclerView.ViewHolder vh = mBaseGridView.findViewHolderForAdapterPosition(pos);
+        if (vh == null) {
+            return false;
+        }
+        return vh.itemView.getLeft() >= 0 && vh.itemView.getRight() < mBaseGridView.getWidth()
+                && vh.itemView.getTop() >= 0 && vh.itemView.getBottom() < mBaseGridView.getHeight();
+    }
+
     boolean canScrollTo(View view) {
         return view.getVisibility() == View.VISIBLE && (!hasFocus() || view.hasFocusable());
     }
@@ -3609,11 +3624,10 @@
         saveContext(recycler, state);
         switch (action) {
             case AccessibilityNodeInfoCompat.ACTION_SCROLL_BACKWARD:
-                // try to focus all the way to the last visible item on the same row.
-                processSelectionMoves(false, -mState.getItemCount());
+                processSelectionMoves(false, -1);
                 break;
             case AccessibilityNodeInfoCompat.ACTION_SCROLL_FORWARD:
-                processSelectionMoves(false, mState.getItemCount());
+                processSelectionMoves(false, 1);
                 break;
         }
         leaveContext();
@@ -3678,11 +3692,12 @@
     public void onInitializeAccessibilityNodeInfo(Recycler recycler, State state,
             AccessibilityNodeInfoCompat info) {
         saveContext(recycler, state);
-        if (mScrollEnabled && !hasCreatedFirstItem()) {
+        int count = state.getItemCount();
+        if (mScrollEnabled && count > 1 && !isItemFullyVisible(0)) {
             info.addAction(AccessibilityNodeInfoCompat.ACTION_SCROLL_BACKWARD);
             info.setScrollable(true);
         }
-        if (mScrollEnabled && !hasCreatedLastItem()) {
+        if (mScrollEnabled && count > 1 && !isItemFullyVisible(count - 1)) {
             info.addAction(AccessibilityNodeInfoCompat.ACTION_SCROLL_FORWARD);
             info.setScrollable(true);
         }
diff --git a/v17/leanback/src/android/support/v17/leanback/widget/HorizontalHoverCardSwitcher.java b/v17/leanback/src/android/support/v17/leanback/widget/HorizontalHoverCardSwitcher.java
index eff822b..8aead19 100644
--- a/v17/leanback/src/android/support/v17/leanback/widget/HorizontalHoverCardSwitcher.java
+++ b/v17/leanback/src/android/support/v17/leanback/widget/HorizontalHoverCardSwitcher.java
@@ -47,7 +47,7 @@
         // end edge with row view's end edge, otherwise align start edges.
         view.measure(MeasureSpec.UNSPECIFIED, MeasureSpec.UNSPECIFIED);
         MarginLayoutParams params = (MarginLayoutParams) view.getLayoutParams();
-        boolean isRtl = ViewCompat.getLayoutDirection(view) == View.LAYOUT_DIRECTION_RTL;
+        boolean isRtl = ViewCompat.getLayoutDirection(view) == ViewCompat.LAYOUT_DIRECTION_RTL;
         if (!isRtl && mCardLeft + view.getMeasuredWidth() > rightLimit) {
             params.leftMargin = rightLimit  - view.getMeasuredWidth();
         } else if (isRtl && mCardLeft < leftLimit) {
diff --git a/v17/leanback/src/android/support/v17/leanback/widget/PlaybackTransportRowPresenter.java b/v17/leanback/src/android/support/v17/leanback/widget/PlaybackTransportRowPresenter.java
index 4c99a3b..e6db33b 100644
--- a/v17/leanback/src/android/support/v17/leanback/widget/PlaybackTransportRowPresenter.java
+++ b/v17/leanback/src/android/support/v17/leanback/widget/PlaybackTransportRowPresenter.java
@@ -380,7 +380,7 @@
             } else {
                 mPositionsLength = 0;
             }
-            mControlsVh.view.setVisibility(View.INVISIBLE);
+            mControlsVh.view.setVisibility(View.GONE);
             mSecondaryControlsVh.view.setVisibility(View.INVISIBLE);
             mDescriptionViewHolder.view.setVisibility(View.INVISIBLE);
             mThumbsBar.setVisibility(View.VISIBLE);
diff --git a/v17/leanback/src/android/support/v17/leanback/widget/RoundedRectHelper.java b/v17/leanback/src/android/support/v17/leanback/widget/RoundedRectHelper.java
index f9382ff..60fedf2 100644
--- a/v17/leanback/src/android/support/v17/leanback/widget/RoundedRectHelper.java
+++ b/v17/leanback/src/android/support/v17/leanback/widget/RoundedRectHelper.java
@@ -13,8 +13,9 @@
  */
 package android.support.v17.leanback.widget;
 
-import android.support.v17.leanback.R;
 import android.os.Build;
+import android.support.annotation.RequiresApi;
+import android.support.v17.leanback.R;
 import android.view.View;
 
 /**
@@ -71,6 +72,7 @@
     /**
      * Implementation used on api 21 (and above).
      */
+    @RequiresApi(21)
     private static final class Api21Impl implements Impl {
         Api21Impl() {
         }
diff --git a/v17/leanback/src/android/support/v17/leanback/widget/ShadowHelper.java b/v17/leanback/src/android/support/v17/leanback/widget/ShadowHelper.java
index e5fb61d..cbdddbf 100644
--- a/v17/leanback/src/android/support/v17/leanback/widget/ShadowHelper.java
+++ b/v17/leanback/src/android/support/v17/leanback/widget/ShadowHelper.java
@@ -14,6 +14,7 @@
 package android.support.v17.leanback.widget;
 
 import android.os.Build;
+import android.support.annotation.RequiresApi;
 import android.view.View;
 
 
@@ -64,6 +65,7 @@
     /**
      * Implementation used on api 21 (and above).
      */
+    @RequiresApi(21)
     private static final class ShadowHelperApi21Impl implements ShadowHelperVersionImpl {
         ShadowHelperApi21Impl() {
         }
diff --git a/v17/leanback/src/android/support/v17/leanback/widget/StaticShadowHelper.java b/v17/leanback/src/android/support/v17/leanback/widget/StaticShadowHelper.java
index 4422d62..436668f 100644
--- a/v17/leanback/src/android/support/v17/leanback/widget/StaticShadowHelper.java
+++ b/v17/leanback/src/android/support/v17/leanback/widget/StaticShadowHelper.java
@@ -16,6 +16,7 @@
 package android.support.v17.leanback.widget;
 
 import android.os.Build;
+import android.support.annotation.RequiresApi;
 import android.view.ViewGroup;
 
 
@@ -64,6 +65,7 @@
     /**
      * Implementation used on JBMR2 (and above).
      */
+    @RequiresApi(19)
     private static final class ShadowHelperJbmr2Impl implements ShadowHelperVersionImpl {
         ShadowHelperJbmr2Impl() {
         }
diff --git a/v17/leanback/src/android/support/v17/leanback/widget/ThumbsBar.java b/v17/leanback/src/android/support/v17/leanback/widget/ThumbsBar.java
index 85dde7f..ca6eaca 100644
--- a/v17/leanback/src/android/support/v17/leanback/widget/ThumbsBar.java
+++ b/v17/leanback/src/android/support/v17/leanback/widget/ThumbsBar.java
@@ -20,6 +20,7 @@
 import android.content.Context;
 import android.graphics.Bitmap;
 import android.support.annotation.RestrictTo;
+import android.support.v17.leanback.R;
 import android.util.AttributeSet;
 import android.util.SparseArray;
 import android.view.View;
@@ -33,24 +34,39 @@
 @RestrictTo(LIBRARY_GROUP)
 public class ThumbsBar extends LinearLayout {
 
-    static final int DEFAULT_NUM_OF_THUMBS = 7;
-
-    int mMinimalMargin = 16;
-    int mNumOfThumbs;
-    int mThumbWidth = 160;
-    int mThumbHeight = 160;
-    int mHeroThumbWidth = 240;
-    int mHeroThumbHeight = 240;
-    int mMeasuredMargin;
+    // initial value for Thumb's number before measuring the screen size
+    int mNumOfThumbs = -1;
+    int mThumbWidthInPixel;
+    int mThumbHeightInPixel;
+    int mHeroThumbWidthInPixel;
+    int mHeroThumbHeightInPixel;
+    int mMeasuredMarginInPixel;
     final SparseArray<Bitmap> mBitmaps = new SparseArray<>();
 
+    // flag to determine if the number of thumbs in thumbs bar is set by user through
+    // setNumberofThumbs API or auto-calculated according to android tv design spec.
+    private boolean mIsUserSets = false;
+
     public ThumbsBar(Context context, AttributeSet attrs) {
         this(context, attrs, 0);
     }
 
     public ThumbsBar(Context context, AttributeSet attrs, int defStyle) {
         super(context, attrs, defStyle);
-        setNumberOfThumbs(DEFAULT_NUM_OF_THUMBS);
+        // According to the spec,
+        // the width of non-hero thumb should be 80% of HeroThumb's Width, i.e. 0.8 * 192dp = 154dp
+        mThumbWidthInPixel = context.getResources().getDimensionPixelSize(
+                R.dimen.lb_playback_transport_thumbs_width);
+        mThumbHeightInPixel = context.getResources().getDimensionPixelSize(
+                R.dimen.lb_playback_transport_thumbs_height);
+        // According to the spec, the width of HeroThumb should be 192dp
+        mHeroThumbHeightInPixel = context.getResources().getDimensionPixelSize(
+                R.dimen.lb_playback_transport_hero_thumbs_width);
+        mHeroThumbWidthInPixel = context.getResources().getDimensionPixelSize(
+                R.dimen.lb_playback_transport_hero_thumbs_height);
+        // According to the spec, the margin between thumbs to be 4dp
+        mMeasuredMarginInPixel = context.getResources().getDimensionPixelSize(
+                R.dimen.lb_playback_transport_thumbs_margin);
     }
 
     /**
@@ -64,8 +80,8 @@
      * Set size of thumb view in pixels
      */
     public void setThumbSize(int width, int height) {
-        mThumbHeight = height;
-        mThumbWidth = width;
+        mThumbHeightInPixel = height;
+        mThumbWidthInPixel = width;
         int heroIndex = getHeroIndex();
         for (int i = 0; i < getChildCount(); i++) {
             if (heroIndex != i) {
@@ -91,8 +107,8 @@
      * Set size of hero thumb view in pixels, it is usually larger than other thumbs.
      */
     public void setHeroThumbSize(int width, int height) {
-        mHeroThumbHeight = height;
-        mHeroThumbWidth = width;
+        mHeroThumbHeightInPixel = height;
+        mHeroThumbWidthInPixel = width;
         int heroIndex = getHeroIndex();
         for (int i = 0; i < getChildCount(); i++) {
             if (heroIndex == i) {
@@ -115,23 +131,34 @@
     }
 
     /**
-     * Set number of thumb views. It must be odd or it will be increasing one.
+     * Set the space between thumbs in pixels
+     */
+    public void setThumbSpace(int spaceInPixel) {
+        mMeasuredMarginInPixel = spaceInPixel;
+        requestLayout();
+    }
+
+    /**
+     * Set number of thumb views.
      */
     public void setNumberOfThumbs(int numOfThumbs) {
-        if (numOfThumbs < 0) {
-            throw new IllegalArgumentException();
-        }
-        if ((numOfThumbs & 1) == 0) {
-            // make it odd number
-            numOfThumbs++;
-        }
+        mIsUserSets = true;
         mNumOfThumbs = numOfThumbs;
+        setNumberOfThumbsInternal();
+    }
+
+    /**
+     * Helper function for setNumberOfThumbs.
+     * Will Update the layout settings in ThumbsBar based on mNumOfThumbs
+     */
+    private void setNumberOfThumbsInternal() {
         while (getChildCount() > mNumOfThumbs) {
             removeView(getChildAt(getChildCount() - 1));
         }
         while (getChildCount() < mNumOfThumbs) {
             View view = createThumbView(this);
-            LinearLayout.LayoutParams lp = new LinearLayout.LayoutParams(mThumbWidth, mThumbHeight);
+            LinearLayout.LayoutParams lp = new LinearLayout.LayoutParams(mThumbWidthInPixel,
+                    mThumbHeightInPixel);
             addView(view, lp);
         }
         int heroIndex = getHeroIndex();
@@ -139,30 +166,69 @@
             View child = getChildAt(i);
             LinearLayout.LayoutParams lp = (LinearLayout.LayoutParams) child.getLayoutParams();
             if (heroIndex == i) {
-                lp.width = mHeroThumbWidth;
-                lp.height = mHeroThumbHeight;
+                lp.width = mHeroThumbWidthInPixel;
+                lp.height = mHeroThumbHeightInPixel;
             } else {
-                lp.width = mThumbWidth;
-                lp.height = mThumbHeight;
+                lp.width = mThumbWidthInPixel;
+                lp.height = mThumbHeightInPixel;
             }
             child.setLayoutParams(lp);
         }
     }
 
+    private static int roundUp(int num, int divisor) {
+        return (num + divisor - 1) / divisor;
+    }
+
+    /**
+     * Helper function to compute how many thumbs should be put in the screen
+     * Assume we should put x's non-hero thumbs in the screen, the equation should be
+     *   192dp (width of hero thumbs) +
+     *   154dp (width of common thumbs) * x +
+     *   4dp (width of the margin between thumbs) * x
+     *     = width
+     * So the calculated number of non-hero thumbs should be (width - 192dp) / 158dp.
+     * If the calculated number of non-hero thumbs is less than 2, it will be updated to 2
+     * or if the calculated number or non-hero thumbs is not an even number, it will be
+     * decremented by one.
+     * This processing is used to make sure the arrangement of non-hero thumbs
+     * in ThumbsBar is symmetrical.
+     * Also there should be a hero thumb in the middle of the ThumbsBar,
+     * the final result should be non-hero thumbs (after processing) + 1.
+     *
+     * @param  widthInPixel measured width in pixel
+     * @return The number of thumbs
+     */
+    private int calculateNumOfThumbs(int widthInPixel) {
+        int nonHeroThumbNum = roundUp(widthInPixel - mHeroThumbWidthInPixel,
+                mThumbWidthInPixel + mMeasuredMarginInPixel);
+        if (nonHeroThumbNum < 2) {
+            // If the calculated number of non-hero thumbs is less than 2,
+            // it will be updated to 2
+            nonHeroThumbNum = 2;
+        } else if ((nonHeroThumbNum & 1) != 0) {
+            // If the calculated number or non-hero thumbs is not an even number,
+            // it will be increased by one.
+            nonHeroThumbNum++;
+        }
+        // Count Hero Thumb to the final result
+        return nonHeroThumbNum + 1;
+    }
+
     @Override
     protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
         super.onMeasure(widthMeasureSpec, heightMeasureSpec);
         int width = getMeasuredWidth();
-        int spaceForMargin = 0;
-        while (mNumOfThumbs > 1) {
-            spaceForMargin = width - mHeroThumbWidth - mThumbWidth * (mNumOfThumbs - 1);
-            if (spaceForMargin < mMinimalMargin * (mNumOfThumbs - 1)) {
-                setNumberOfThumbs(mNumOfThumbs - 2);
-            } else {
-                break;
+        // If the number of thumbs in ThumbsBar is not set by user explicitly, it will be
+        // recalculated based on Android TV Design Spec
+        if (!mIsUserSets) {
+            int numOfThumbs = calculateNumOfThumbs(width);
+            // Set new number of thumbs when calculation result is different with current number
+            if (mNumOfThumbs != numOfThumbs) {
+                mNumOfThumbs = numOfThumbs;
+                setNumberOfThumbsInternal();
             }
         }
-        mMeasuredMargin = mNumOfThumbs > 0 ? spaceForMargin / (mNumOfThumbs - 1) : 0;
     }
 
     @Override
@@ -177,7 +243,7 @@
         int heroCenter = getPaddingTop() + heroView.getMeasuredHeight() / 2;
 
         for (int i = heroIndex - 1; i >= 0; i--) {
-            heroLeft -= mMeasuredMargin;
+            heroLeft -= mMeasuredMarginInPixel;
             View child = getChildAt(i);
             child.layout(heroLeft - child.getMeasuredWidth(),
                     heroCenter - child.getMeasuredHeight() / 2,
@@ -186,7 +252,7 @@
             heroLeft -= child.getMeasuredWidth();
         }
         for (int i = heroIndex + 1; i < mNumOfThumbs; i++) {
-            heroRight += mMeasuredMargin;
+            heroRight += mMeasuredMarginInPixel;
             View child = getChildAt(i);
             child.layout(heroRight,
                     heroCenter - child.getMeasuredHeight() / 2,
diff --git a/v17/leanback/src/android/support/v17/leanback/widget/TitleHelper.java b/v17/leanback/src/android/support/v17/leanback/widget/TitleHelper.java
index f12d8d0..3aba59d 100644
--- a/v17/leanback/src/android/support/v17/leanback/widget/TitleHelper.java
+++ b/v17/leanback/src/android/support/v17/leanback/widget/TitleHelper.java
@@ -46,7 +46,7 @@
                 return mTitleView;
             }
             final boolean isRtl = ViewCompat.getLayoutDirection(focused)
-                    == View.LAYOUT_DIRECTION_RTL;
+                    == ViewCompat.LAYOUT_DIRECTION_RTL;
             final int forward = isRtl ? View.FOCUS_LEFT : View.FOCUS_RIGHT;
             if (mTitleView.hasFocus() && (direction == View.FOCUS_DOWN || direction == forward)) {
                 return mSceneRoot;
diff --git a/v17/leanback/src/android/support/v17/leanback/widget/Visibility.java b/v17/leanback/src/android/support/v17/leanback/widget/Visibility.java
new file mode 100644
index 0000000..b16a2f9
--- /dev/null
+++ b/v17/leanback/src/android/support/v17/leanback/widget/Visibility.java
@@ -0,0 +1,32 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.support.v17.leanback.widget;
+
+import static android.support.annotation.RestrictTo.Scope.LIBRARY_GROUP;
+
+import android.support.annotation.IntDef;
+import android.support.annotation.RestrictTo;
+import android.view.View;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+
+/** @hide */
+@RestrictTo(LIBRARY_GROUP)
+@IntDef({View.VISIBLE, View.INVISIBLE, View.GONE})
+@Retention(RetentionPolicy.SOURCE)
+public @interface Visibility {}
diff --git a/v17/leanback/src/android/support/v17/leanback/widget/WindowAlignment.java b/v17/leanback/src/android/support/v17/leanback/widget/WindowAlignment.java
index a651b2e..3ddb6f0 100644
--- a/v17/leanback/src/android/support/v17/leanback/widget/WindowAlignment.java
+++ b/v17/leanback/src/android/support/v17/leanback/widget/WindowAlignment.java
@@ -254,27 +254,49 @@
                 }
             }
             if (!isMaxUnknown && !isMinUnknown) {
-                if (!mReversedFlow ? (mWindowAlignment & WINDOW_ALIGN_LOW_EDGE) != 0
-                        : (mWindowAlignment & WINDOW_ALIGN_HIGH_EDGE) != 0) {
-                    if (!mReversedFlow ? isPreferKeylineOverLowEdge()
-                            : isPreferKeylineOverHighEdge()) {
-                        // if we prefer key line, might align max child to key line for minScroll
-                        mMinScroll = Math.min(mMinScroll,
-                                calculateScrollToKeyLine(maxChildViewCenter, keyLine));
-                    } else {
-                        // don't over scroll max
-                        mMaxScroll = Math.max(mMinScroll, mMaxScroll);
+                if (!mReversedFlow) {
+                    if ((mWindowAlignment & WINDOW_ALIGN_LOW_EDGE) != 0) {
+                        if (isPreferKeylineOverLowEdge()) {
+                            // if we prefer key line, might align max child to key line for
+                            // minScroll
+                            mMinScroll = Math.min(mMinScroll,
+                                    calculateScrollToKeyLine(maxChildViewCenter, keyLine));
+                        } else {
+                            // don't over scroll max
+                            mMaxScroll = Math.max(mMinScroll, mMaxScroll);
+                        }
+                    } else if ((mWindowAlignment & WINDOW_ALIGN_HIGH_EDGE) != 0) {
+                        if (isPreferKeylineOverHighEdge()) {
+                            // if we prefer key line, might align min child to key line for
+                            // maxScroll
+                            mMaxScroll = Math.max(mMaxScroll,
+                                    calculateScrollToKeyLine(minChildViewCenter, keyLine));
+                        } else {
+                            // don't over scroll min
+                            mMinScroll = Math.min(mMinScroll, mMaxScroll);
+                        }
                     }
-                } else if (!mReversedFlow ? (mWindowAlignment & WINDOW_ALIGN_HIGH_EDGE) != 0
-                        : (mWindowAlignment & WINDOW_ALIGN_LOW_EDGE) != 0) {
-                    if (!mReversedFlow ? isPreferKeylineOverHighEdge()
-                            : isPreferKeylineOverLowEdge()) {
-                        // if we prefer key line, might align min child to key line for maxScroll
-                        mMaxScroll = Math.max(mMaxScroll,
-                                calculateScrollToKeyLine(minChildViewCenter, keyLine));
-                    } else {
-                        // don't over scroll min
-                        mMinScroll = Math.min(mMinScroll, mMaxScroll);
+                } else {
+                    if ((mWindowAlignment & WINDOW_ALIGN_LOW_EDGE) != 0) {
+                        if (isPreferKeylineOverLowEdge()) {
+                            // if we prefer key line, might align min child to key line for
+                            // maxScroll
+                            mMaxScroll = Math.max(mMaxScroll,
+                                    calculateScrollToKeyLine(minChildViewCenter, keyLine));
+                        } else {
+                            // don't over scroll min
+                            mMinScroll = Math.min(mMinScroll, mMaxScroll);
+                        }
+                    } else if ((mWindowAlignment & WINDOW_ALIGN_HIGH_EDGE) != 0) {
+                        if (isPreferKeylineOverHighEdge()) {
+                            // if we prefer key line, might align max child to key line for
+                            // minScroll
+                            mMinScroll = Math.min(mMinScroll,
+                                    calculateScrollToKeyLine(maxChildViewCenter, keyLine));
+                        } else {
+                            // don't over scroll max
+                            mMaxScroll = Math.max(mMinScroll, mMaxScroll);
+                        }
                     }
                 }
             }
diff --git a/v17/leanback/tests/java/android/support/v17/leanback/widget/GridWidgetTest.java b/v17/leanback/tests/java/android/support/v17/leanback/widget/GridWidgetTest.java
index 1a173b4..86fb4eb 100644
--- a/v17/leanback/tests/java/android/support/v17/leanback/widget/GridWidgetTest.java
+++ b/v17/leanback/tests/java/android/support/v17/leanback/widget/GridWidgetTest.java
@@ -1419,6 +1419,27 @@
     }
 
     @Test
+    public void testItemMovedHorizontalRtl() throws Throwable {
+        Intent intent = new Intent();
+        intent.putExtra(GridActivity.EXTRA_LAYOUT_RESOURCE_ID,
+                R.layout.horizontal_linear_rtl);
+        intent.putExtra(GridActivity.EXTRA_STAGGERED, false);
+        intent.putExtra(GridActivity.EXTRA_ITEMS, new int[] {40, 40, 40});
+        initActivity(intent);
+        mOrientation = BaseGridView.HORIZONTAL;
+        mNumRows = 1;
+
+        performAndWaitForAnimation(new Runnable() {
+            @Override
+            public void run() {
+                mActivity.moveItem(0, 1, true);
+            }
+        });
+        assertEquals(mGridView.getWidth() - mGridView.getPaddingRight(),
+                mGridView.findViewHolderForAdapterPosition(0).itemView.getRight());
+    }
+
+    @Test
     public void testScrollSecondaryCannotScroll() throws Throwable {
         Intent intent = new Intent();
         intent.putExtra(GridActivity.EXTRA_LAYOUT_RESOURCE_ID,
@@ -3888,6 +3909,108 @@
         assertTrue(selectedPosition2 < selectedPosition1);
     }
 
+    @Test
+    public void testAccessibilityScrollForwardHalfVisible() throws Throwable {
+        Intent intent = new Intent();
+        intent.putExtra(GridActivity.EXTRA_LAYOUT_RESOURCE_ID, R.layout.vertical_linear);
+        intent.putExtra(GridActivity.EXTRA_CHILD_LAYOUT_ID, R.layout.item_button_at_bottom);
+        intent.putExtra(GridActivity.EXTRA_ITEMS,  new int[]{});
+        intent.putExtra(GridActivity.EXTRA_STAGGERED, false);
+        initActivity(intent);
+        mOrientation = BaseGridView.VERTICAL;
+        mNumRows = 1;
+
+        int height = mGridView.getHeight() - mGridView.getPaddingTop()
+                - mGridView.getPaddingBottom();
+        final int childHeight = height - mGridView.getVerticalSpacing() - 100;
+        mActivityTestRule.runOnUiThread(new Runnable() {
+            @Override
+            public void run() {
+                mGridView.setWindowAlignment(BaseGridView.WINDOW_ALIGN_NO_EDGE);
+                mGridView.setWindowAlignmentOffset(100);
+                mGridView.setWindowAlignmentOffsetPercent(BaseGridView
+                        .WINDOW_ALIGN_OFFSET_PERCENT_DISABLED);
+                mGridView.setItemAlignmentOffset(0);
+                mGridView.setItemAlignmentOffsetPercent(BaseGridView
+                        .ITEM_ALIGN_OFFSET_PERCENT_DISABLED);
+            }
+        });
+        mActivity.addItems(0, new int[]{childHeight, childHeight});
+        waitForItemAnimation();
+        setSelectedPosition(0);
+
+        final RecyclerViewAccessibilityDelegate delegateCompat = mGridView
+                .getCompatAccessibilityDelegate();
+        final AccessibilityNodeInfoCompat info = AccessibilityNodeInfoCompat.obtain();
+        mActivityTestRule.runOnUiThread(new Runnable() {
+            @Override
+            public void run() {
+                delegateCompat.onInitializeAccessibilityNodeInfo(mGridView, info);
+            }
+        });
+        assertTrue("test sanity", info.isScrollable());
+        mActivityTestRule.runOnUiThread(new Runnable() {
+            @Override
+            public void run() {
+                delegateCompat.performAccessibilityAction(mGridView,
+                        AccessibilityNodeInfoCompat.ACTION_SCROLL_FORWARD, null);
+            }
+        });
+        waitForScrollIdle(mVerifyLayout);
+        assertEquals(1, mGridView.getSelectedPosition());
+    }
+
+    @Test
+    public void testAccessibilityScrollBackwardHalfVisible() throws Throwable {
+        Intent intent = new Intent();
+        intent.putExtra(GridActivity.EXTRA_LAYOUT_RESOURCE_ID, R.layout.vertical_linear);
+        intent.putExtra(GridActivity.EXTRA_CHILD_LAYOUT_ID, R.layout.item_button_at_top);
+        intent.putExtra(GridActivity.EXTRA_ITEMS,  new int[]{});
+        intent.putExtra(GridActivity.EXTRA_STAGGERED, false);
+        initActivity(intent);
+        mOrientation = BaseGridView.VERTICAL;
+        mNumRows = 1;
+
+        int height = mGridView.getHeight() - mGridView.getPaddingTop()
+                - mGridView.getPaddingBottom();
+        final int childHeight = height - mGridView.getVerticalSpacing() - 100;
+        mActivityTestRule.runOnUiThread(new Runnable() {
+            @Override
+            public void run() {
+                mGridView.setWindowAlignment(BaseGridView.WINDOW_ALIGN_NO_EDGE);
+                mGridView.setWindowAlignmentOffset(100);
+                mGridView.setWindowAlignmentOffsetPercent(BaseGridView
+                        .WINDOW_ALIGN_OFFSET_PERCENT_DISABLED);
+                mGridView.setItemAlignmentOffset(0);
+                mGridView.setItemAlignmentOffsetPercent(BaseGridView
+                        .ITEM_ALIGN_OFFSET_PERCENT_DISABLED);
+            }
+        });
+        mActivity.addItems(0, new int[]{childHeight, childHeight});
+        waitForItemAnimation();
+        setSelectedPosition(1);
+
+        final RecyclerViewAccessibilityDelegate delegateCompat = mGridView
+                .getCompatAccessibilityDelegate();
+        final AccessibilityNodeInfoCompat info = AccessibilityNodeInfoCompat.obtain();
+        mActivityTestRule.runOnUiThread(new Runnable() {
+            @Override
+            public void run() {
+                delegateCompat.onInitializeAccessibilityNodeInfo(mGridView, info);
+            }
+        });
+        assertTrue("test sanity", info.isScrollable());
+        mActivityTestRule.runOnUiThread(new Runnable() {
+            @Override
+            public void run() {
+                delegateCompat.performAccessibilityAction(mGridView,
+                        AccessibilityNodeInfoCompat.ACTION_SCROLL_BACKWARD, null);
+            }
+        });
+        waitForScrollIdle(mVerifyLayout);
+        assertEquals(0, mGridView.getSelectedPosition());
+    }
+
     void slideInAndWaitIdle() throws Throwable {
         slideInAndWaitIdle(5000);
     }
@@ -4884,14 +5007,14 @@
 
     void prepareKeyLineTest(int numItems) throws Throwable {
         Intent intent = new Intent();
-        intent.putExtra(GridActivity.EXTRA_LAYOUT_RESOURCE_ID, R.layout.vertical_linear);
+        intent.putExtra(GridActivity.EXTRA_LAYOUT_RESOURCE_ID, R.layout.horizontal_linear);
         int[] items = new int[numItems];
         for (int i = 0; i < items.length; i++) {
             items[i] = 32;
         }
         intent.putExtra(GridActivity.EXTRA_ITEMS, items);
         intent.putExtra(GridActivity.EXTRA_STAGGERED, false);
-        mOrientation = BaseGridView.VERTICAL;
+        mOrientation = BaseGridView.HORIZONTAL;
         mNumRows = 1;
 
         initActivity(intent);
@@ -4932,51 +5055,76 @@
             final boolean preferKeyLineOverHigh,
             ItemAt assertFirstItemLocation,
             ItemAt assertLastItemLocation) throws Throwable {
+        TestPreferKeyLineOptions options = new TestPreferKeyLineOptions();
+        options.mAssertItemLocations = new ItemAt[] {assertFirstItemLocation,
+                assertLastItemLocation};
+        options.mPreferKeyLineOverLow = preferKeyLineOverLow;
+        options.mPreferKeyLineOverHigh = preferKeyLineOverHigh;
+        options.mWindowAlignment = windowAlignment;
+
+        options.mRtl = false;
+        testPreferKeyLine(options);
+
+        options.mRtl = true;
+        testPreferKeyLine(options);
+    }
+
+    static class TestPreferKeyLineOptions {
+        int mWindowAlignment;
+        boolean mPreferKeyLineOverLow;
+        boolean mPreferKeyLineOverHigh;
+        ItemAt[] mAssertItemLocations;
+        boolean mRtl;
+    }
+
+    public void testPreferKeyLine(final TestPreferKeyLineOptions options) throws Throwable {
         startWaitLayout();
         mActivityTestRule.runOnUiThread(new Runnable() {
             @Override
             public void run() {
-                mGridView.setWindowAlignment(windowAlignment);
+                if (options.mRtl) {
+                    mGridView.setLayoutDirection(View.LAYOUT_DIRECTION_RTL);
+                } else {
+                    mGridView.setLayoutDirection(View.LAYOUT_DIRECTION_LTR);
+                }
+                mGridView.setWindowAlignment(options.mWindowAlignment);
                 mGridView.setWindowAlignmentOffsetPercent(50);
                 mGridView.setWindowAlignmentOffset(0);
-                mGridView.setWindowAlignmentPreferKeyLineOverLowEdge(preferKeyLineOverLow);
-                mGridView.setWindowAlignmentPreferKeyLineOverHighEdge(preferKeyLineOverHigh);
+                mGridView.setWindowAlignmentPreferKeyLineOverLowEdge(options.mPreferKeyLineOverLow);
+                mGridView.setWindowAlignmentPreferKeyLineOverHighEdge(
+                        options.mPreferKeyLineOverHigh);
             }
         });
         waitForLayout();
 
-        final int lowPadding = mGridView.getPaddingTop();
-        final int highPadding = mGridView.getHeight() - mGridView.getPaddingBottom();
-        final int windowAlignCenter = mGridView.getHeight() / 2;
+        final int paddingStart = mGridView.getPaddingStart();
+        final int paddingEnd = mGridView.getPaddingEnd();
+        final int windowAlignCenter = mGridView.getWidth() / 2;
 
-        setSelectedPosition(assertFirstItemLocation.mScrollPosition);
-        View view = mGridView.findViewHolderForAdapterPosition(assertFirstItemLocation.mPosition)
-                .itemView;
-        switch (assertFirstItemLocation.mLocation) {
-            case ITEM_AT_LOW:
-                assertEquals(lowPadding, view.getTop());
-                break;
-            case ITEM_AT_HIGH:
-                assertEquals(highPadding, view.getBottom());
-                break;
-            case ITEM_AT_KEY_LINE:
-                assertEquals(windowAlignCenter, view.getTop() + view.getHeight() / 2, DELTA);
-                break;
-        }
-
-        setSelectedPosition(assertLastItemLocation.mScrollPosition);
-        view = mGridView.findViewHolderForAdapterPosition(assertLastItemLocation.mPosition)
-                .itemView;
-        switch (assertLastItemLocation.mLocation) {
-            case ITEM_AT_LOW:
-                assertEquals(lowPadding, view.getTop());
-                break;
-            case ITEM_AT_HIGH:
-                assertEquals(highPadding, view.getBottom());
-                break;
-            case ITEM_AT_KEY_LINE:
-                assertEquals(windowAlignCenter, view.getTop() + view.getHeight() / 2, DELTA);
-                break;
+        for (int i = 0; i < options.mAssertItemLocations.length; i++) {
+            ItemAt assertItemLocation = options.mAssertItemLocations[i];
+            setSelectedPosition(assertItemLocation.mScrollPosition);
+            View view = mGridView.findViewHolderForAdapterPosition(assertItemLocation.mPosition)
+                    .itemView;
+            switch (assertItemLocation.mLocation) {
+                case ITEM_AT_LOW:
+                    if (options.mRtl) {
+                        assertEquals(mGridView.getWidth() - paddingStart, view.getRight());
+                    } else {
+                        assertEquals(paddingStart, view.getLeft());
+                    }
+                    break;
+                case ITEM_AT_HIGH:
+                    if (options.mRtl) {
+                        assertEquals(paddingEnd, view.getLeft());
+                    } else {
+                        assertEquals(mGridView.getWidth() - paddingEnd, view.getRight());
+                    }
+                    break;
+                case ITEM_AT_KEY_LINE:
+                    assertEquals(windowAlignCenter, (view.getLeft() + view.getRight()) / 2, DELTA);
+                    break;
+            }
         }
     }
 
diff --git a/v17/leanback/tests/java/android/support/v17/leanback/widget/ThumbsBarTest.java b/v17/leanback/tests/java/android/support/v17/leanback/widget/ThumbsBarTest.java
new file mode 100644
index 0000000..394d723
--- /dev/null
+++ b/v17/leanback/tests/java/android/support/v17/leanback/widget/ThumbsBarTest.java
@@ -0,0 +1,330 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.support.v17.leanback.widget;
+
+import static junit.framework.Assert.assertEquals;
+
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import android.content.Context;
+import android.support.test.InstrumentationRegistry;
+import android.support.test.filters.SmallTest;
+import android.support.test.runner.AndroidJUnit4;
+import android.support.v17.leanback.R;
+import android.view.View;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mockito;
+
+@SmallTest
+@RunWith(AndroidJUnit4.class)
+
+public class ThumbsBarTest {
+    private Context mContext;
+    private ThumbsBar mBar;
+
+    /**
+     * Check ThumbsBar's initialization based on the constructor
+     */
+    @Test
+    public void checkThumbsBarInitialize() {
+        mContext = InstrumentationRegistry.getInstrumentation().getTargetContext();
+        InstrumentationRegistry.getInstrumentation().runOnMainSync(new Runnable() {
+            @Override
+            public void run() {
+                mBar = Mockito.spy(new ThumbsBar(mContext, null));
+            }
+        });
+        assertEquals(mBar.mThumbHeightInPixel, mContext.getResources().getDimensionPixelSize(
+                R.dimen.lb_playback_transport_thumbs_height));
+        assertEquals(mBar.mThumbWidthInPixel, mContext.getResources().getDimensionPixelSize(
+                R.dimen.lb_playback_transport_thumbs_width));
+        assertEquals(mBar.mHeroThumbHeightInPixel, mContext.getResources().getDimensionPixelSize(
+                R.dimen.lb_playback_transport_hero_thumbs_height));
+        assertEquals(mBar.mHeroThumbWidthInPixel, mContext.getResources().getDimensionPixelSize(
+                R.dimen.lb_playback_transport_hero_thumbs_width));
+    }
+
+    /**
+     * Check getHeroIndex method when input is an even number
+     */
+    @Test
+    public void checkGetHeroIndexOnEvenNumber() {
+        mContext = InstrumentationRegistry.getInstrumentation().getTargetContext();
+        InstrumentationRegistry.getInstrumentation().runOnMainSync(new Runnable() {
+            @Override
+            public void run() {
+                mBar = Mockito.spy(new ThumbsBar(mContext, null));
+            }
+        });
+        int childCountForTest = 4;
+        // according to the algorithm, hero thumb's index should be childCounts / 2
+        int expectedHeroIndex = 2;
+        when(mBar.getChildCount()).thenReturn(childCountForTest);
+        assertEquals(mBar.getHeroIndex(), expectedHeroIndex);
+    }
+
+    /**
+     * Check getHeroIndex method when input is an odd number.
+     */
+    @Test
+    public void checkGetHeroIndexOnOddNumber() {
+        mContext = InstrumentationRegistry.getInstrumentation().getTargetContext();
+        InstrumentationRegistry.getInstrumentation().runOnMainSync(new Runnable() {
+            @Override
+            public void run() {
+                mBar = Mockito.spy(new ThumbsBar(mContext, null));
+            }
+        });
+        int childCountForTest = 5;
+        // according to the algorithm, hero thumb's index should be childCounts / 2
+        int expectedHeroIndex = 2;
+        when(mBar.getChildCount()).thenReturn(childCountForTest);
+        assertEquals(mBar.getHeroIndex(), expectedHeroIndex);
+    }
+
+    /**
+     * Check setThumbSize method.
+     */
+    @Test
+    public void checkSetThumbSize() {
+        mContext = InstrumentationRegistry.getInstrumentation().getTargetContext();
+        InstrumentationRegistry.getInstrumentation().runOnMainSync(new Runnable() {
+            @Override
+            public void run() {
+                mBar = Mockito.spy(new ThumbsBar(mContext, null));
+            }
+        });
+        int screenWidthInPixelForTest = 2560;
+        int screenHeightInPixelForTest = 1600;
+        int thumbsWidthInPixelForTest = 128;
+        int thumbsHeightInPixelForTest = 256;
+        // set screen size explicitly so the thumbs bar will have child view inside of it
+        mBar.measure(View.MeasureSpec.makeMeasureSpec(screenWidthInPixelForTest,
+                View.MeasureSpec.EXACTLY),
+                View.MeasureSpec.makeMeasureSpec(screenHeightInPixelForTest,
+                        View.MeasureSpec.EXACTLY));
+
+        mBar.setThumbSize(thumbsWidthInPixelForTest, thumbsHeightInPixelForTest);
+        // Verify the behavior of setThumbSize method
+        assertEquals(mBar.mThumbWidthInPixel, thumbsWidthInPixelForTest);
+        assertEquals(mBar.mThumbHeightInPixel, thumbsHeightInPixelForTest);
+        // iterate through all child view to test if its width/ height has been set successfully
+        for (int i = 0; i < mBar.getChildCount(); i++) {
+            if (i != mBar.getHeroIndex()) {
+                assertEquals(mBar.getChildAt(i).getLayoutParams().width,
+                        thumbsWidthInPixelForTest);
+                assertEquals(mBar.getChildAt(i).getLayoutParams().height,
+                        thumbsHeightInPixelForTest);
+            } else {
+                assertEquals(mBar.getChildAt(i).getLayoutParams().width,
+                        mContext.getResources().getDimensionPixelSize(
+                                R.dimen.lb_playback_transport_hero_thumbs_width));
+                assertEquals(mBar.getChildAt(i).getLayoutParams().height,
+                        mContext.getResources().getDimensionPixelSize(
+                                R.dimen.lb_playback_transport_hero_thumbs_height));
+            }
+        }
+    }
+
+    /**
+     * Check setHeroThumbSize method.
+     */
+    @Test
+    public void checkSetHeroThumbSize() {
+        mContext = InstrumentationRegistry.getInstrumentation().getTargetContext();
+        InstrumentationRegistry.getInstrumentation().runOnMainSync(new Runnable() {
+            @Override
+            public void run() {
+                mBar = Mockito.spy(new ThumbsBar(mContext, null));
+            }
+        });
+        int screenWidthInPixelForTest = 2560;
+        int screenHeightInPixelForTest = 1600;
+        int HeroThumbsWidthInPixelForTest = 256;
+        int HeroThumbsHeightInPixelForTest = 512;
+        // set screen size explicitly so the thumbs bar will have child view inside of it
+        mBar.measure(View.MeasureSpec.makeMeasureSpec(
+                screenWidthInPixelForTest, View.MeasureSpec.EXACTLY),
+                View.MeasureSpec.makeMeasureSpec(
+                        screenHeightInPixelForTest, View.MeasureSpec.EXACTLY));
+        mBar.setHeroThumbSize(HeroThumbsWidthInPixelForTest, HeroThumbsHeightInPixelForTest);
+        // Verify the behavior of setThumbSize method
+        assertEquals(mBar.mHeroThumbWidthInPixel, HeroThumbsWidthInPixelForTest);
+        assertEquals(mBar.mHeroThumbHeightInPixel, HeroThumbsHeightInPixelForTest);
+        // iterate through all child view to test if its width/ height has been set successfully
+        for (int i = 0; i < mBar.getChildCount(); i++) {
+            if (i != mBar.getHeroIndex()) {
+                assertEquals(mBar.getChildAt(i).getLayoutParams().width,
+                        mContext.getResources().getDimensionPixelSize(
+                                R.dimen.lb_playback_transport_thumbs_width));
+                assertEquals(mBar.getChildAt(i).getLayoutParams().height,
+                        mContext.getResources().getDimensionPixelSize(
+                                R.dimen.lb_playback_transport_thumbs_height));
+            } else {
+                assertEquals(mBar.getChildAt(i).getLayoutParams().width,
+                        HeroThumbsWidthInPixelForTest);
+                assertEquals(mBar.getChildAt(i).getLayoutParams().height,
+                        HeroThumbsHeightInPixelForTest);
+            }
+        }
+    }
+
+    /**
+     * Check setThumbSpace method.
+     */
+    @Test
+    public void checkSetThumbSpace() {
+        mContext = InstrumentationRegistry.getInstrumentation().getTargetContext();
+        InstrumentationRegistry.getInstrumentation().runOnMainSync(new Runnable() {
+            @Override
+            public void run() {
+                mBar = Mockito.spy(new ThumbsBar(mContext, null));
+            }
+        });
+        int thumbSpaceInPixelForTest = 48;
+        mBar.setThumbSpace(thumbSpaceInPixelForTest);
+        assertEquals(mBar.mMeasuredMarginInPixel, thumbSpaceInPixelForTest);
+        verify(mBar).requestLayout();
+    }
+
+    /**
+     * check calculateNumberOfThumbs method when the result from roundUp function is less than 2
+     *
+     * Firstly, to make sure the test cases can run on different devices with different screen
+     * density (i.e. The return value from roundUp function should be the same no matter what kind
+     * of device/ emulator is connected),
+     * the screen width for test is set using dp, the pixel value will be computed by
+     * multiplying context.getResources().getDisplayMetrics().density
+     *
+     * In this test case, the screen width is set to 240 in dp, so the calculation result should
+     * be 1. According to the algorithm of calculateNumOfThumbs, it should be reassigned to 2 and
+     * the final result should be 3 after counting the hero thumb.
+     */
+    @Test
+    public void checkCalculateNumberOfThumbs1() {
+        mContext = InstrumentationRegistry.getInstrumentation().getTargetContext();
+        InstrumentationRegistry.getInstrumentation().runOnMainSync(new Runnable() {
+            @Override
+            public void run() {
+                mBar = Mockito.spy(new ThumbsBar(mContext, null));
+            }
+        });
+        int screenWidthInPixelForTest =
+                (int) (240 * mContext.getResources().getDisplayMetrics().density);
+        int screenHeightInPixelForTest =
+                (int) (240 * mContext.getResources().getDisplayMetrics().density);
+        int expectedChildCounts = 3;
+        mBar.measure(View.MeasureSpec.makeMeasureSpec(
+                screenWidthInPixelForTest, View.MeasureSpec.EXACTLY),
+                View.MeasureSpec.makeMeasureSpec(
+                        screenHeightInPixelForTest, View.MeasureSpec.EXACTLY));
+        assertEquals(mBar.getChildCount(), expectedChildCounts);
+    }
+
+    /**
+     * check calculateNumberOfThumbs method when the result from roundUp function is an odd number
+     * and larger than 2.
+     *
+     * In this test case, the screen width is set to 680 in dp, so the calculation result should
+     * be 3. According to the algorithm of calculateNumOfThumbs, it should be incremented by 1, so
+     * the final result is 5 after counting the hero thumb.
+     */
+    @Test
+    public void checkCalculateNumberOfThumbs2() {
+        mContext = InstrumentationRegistry.getInstrumentation().getTargetContext();
+        InstrumentationRegistry.getInstrumentation().runOnMainSync(new Runnable() {
+            @Override
+            public void run() {
+                mBar = Mockito.spy(new ThumbsBar(mContext, null));
+            }
+        });
+        int screenWidthInPixelForTest =
+                (int) (680 * mContext.getResources().getDisplayMetrics().density);
+        int screenHeightInPixelForTest =
+                (int) (680 * mContext.getResources().getDisplayMetrics().density);
+        int expectedChildCounts = 5;
+        mBar.measure(View.MeasureSpec.makeMeasureSpec(
+                screenWidthInPixelForTest, View.MeasureSpec.EXACTLY),
+                View.MeasureSpec.makeMeasureSpec(
+                        screenHeightInPixelForTest, View.MeasureSpec.EXACTLY));
+        assertEquals(mBar.getChildCount(), expectedChildCounts);
+    }
+
+    /**
+     * check calculateNumberOfThumbs method when the result from roundUp function is an even number
+     * and larger than 2
+     *
+     * In this test case, the screen width is set to 800 in dp, so the calculation result should
+     * be 4. Finally the result is expected to be 5 after counting the hero thumb.
+     */
+    @Test
+    public void checkCalculateNumberOfThumbs3() {
+        mContext = InstrumentationRegistry.getInstrumentation().getTargetContext();
+        InstrumentationRegistry.getInstrumentation().runOnMainSync(new Runnable() {
+            @Override
+            public void run() {
+                mBar = Mockito.spy(new ThumbsBar(mContext, null));
+            }
+        });
+        int screenWidthInPixelForTest =
+                (int) (800 * mContext.getResources().getDisplayMetrics().density);
+        int screenHeightInPixelForTest =
+                (int) (800 * mContext.getResources().getDisplayMetrics().density);
+        int expectedChildCounts = 5;
+        mBar.measure(View.MeasureSpec.makeMeasureSpec(
+                screenWidthInPixelForTest, View.MeasureSpec.EXACTLY),
+                View.MeasureSpec.makeMeasureSpec(
+                        screenHeightInPixelForTest, View.MeasureSpec.EXACTLY));
+        assertEquals(mBar.getChildCount(), expectedChildCounts);
+    }
+
+    /**
+     * check setNumberOfThumbs method
+     *
+     * When user calling setNumberOfThumbs(int numOfThumbs) method. The flag mIsUserSets will be
+     * toggled to true to honor user's choice, and the result of child view's number should
+     * not be impacted by calculateNumberOfThumbs(int widthInPixel) method.
+     *
+     * In this test case, the screen width is set to 960 in dp, the calculation result from
+     * calculateNumberOfThumbs method should be 5. But after calling setNumberOfThumbs function to
+     * set thumbs' number to 3, this value should not impact the final result.
+     */
+    @Test
+    public void checkSetNumberOfThumbs() {
+        mContext = InstrumentationRegistry.getInstrumentation().getTargetContext();
+        InstrumentationRegistry.getInstrumentation().runOnMainSync(new Runnable() {
+            @Override
+            public void run() {
+                mBar = Mockito.spy(new ThumbsBar(mContext, null));
+            }
+        });
+        int screenWidthInPixelForTest =
+                (int) (960 * mContext.getResources().getDisplayMetrics().density);
+        int screenHeightInPixelForTest =
+                (int) (960 * mContext.getResources().getDisplayMetrics().density);
+        int numberOfThumbs = 3;
+        mBar.setNumberOfThumbs(numberOfThumbs);
+        mBar.measure(View.MeasureSpec.makeMeasureSpec(
+                screenWidthInPixelForTest, View.MeasureSpec.EXACTLY),
+                View.MeasureSpec.makeMeasureSpec(
+                        screenHeightInPixelForTest, View.MeasureSpec.EXACTLY));
+        assertEquals(mBar.getChildCount(), numberOfThumbs);
+    }
+}
diff --git a/v17/leanback/tests/res/layout/item_button_at_bottom.xml b/v17/leanback/tests/res/layout/item_button_at_bottom.xml
new file mode 100644
index 0000000..8afc622
--- /dev/null
+++ b/v17/leanback/tests/res/layout/item_button_at_bottom.xml
@@ -0,0 +1,32 @@
+<!--
+  ~ Copyright (C) 2017 The Android Open Source Project
+  ~
+  ~ Licensed under the Apache License, Version 2.0 (the "License");
+  ~ you may not use this file except in compliance with the License.
+  ~ You may obtain a copy of the License at
+  ~
+  ~      http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing, software
+  ~ distributed under the License is distributed on an "AS IS" BASIS,
+  ~ WITHOUT 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="400dp"
+    android:layout_height="400dp"
+    >
+    <TextView
+        android:layout_alignParentTop="true"
+        android:text="unfocusable text"
+        android:layout_width="match_parent"
+        android:layout_height="50dp"/>
+    <Button
+        android:layout_alignParentBottom="true"
+        android:text="button"
+        android:focusable="true"
+        android:layout_width="match_parent"
+        android:layout_height="50dp"/>
+</RelativeLayout>
diff --git a/v17/leanback/tests/res/layout/item_button_at_top.xml b/v17/leanback/tests/res/layout/item_button_at_top.xml
new file mode 100644
index 0000000..5199193
--- /dev/null
+++ b/v17/leanback/tests/res/layout/item_button_at_top.xml
@@ -0,0 +1,33 @@
+<!--
+  ~ Copyright (C) 2017 The Android Open Source Project
+  ~
+  ~ Licensed under the Apache License, Version 2.0 (the "License");
+  ~ you may not use this file except in compliance with the License.
+  ~ You may obtain a copy of the License at
+  ~
+  ~      http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing, software
+  ~ distributed under the License is distributed on an "AS IS" BASIS,
+  ~ WITHOUT 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="400dp"
+    android:layout_height="400dp"
+    >
+    <TextView
+        android:layout_alignParentBottom="true"
+        android:text="unfocusable text"
+        android:layout_width="match_parent"
+        android:layout_height="50dp"/>
+    <Button
+        android:layout_alignParentTop="true"
+        android:text="button"
+        android:focusable="true"
+        android:layout_width="match_parent"
+        android:layout_height="50dp"/>
+</RelativeLayout>
diff --git a/v17/preference-leanback/lint-baseline.xml b/v17/preference-leanback/lint-baseline.xml
index 724d38a..57cbf2b 100644
--- a/v17/preference-leanback/lint-baseline.xml
+++ b/v17/preference-leanback/lint-baseline.xml
@@ -1,5 +1,5 @@
 <?xml version="1.0" encoding="UTF-8"?>
-<issues format="4" by="lint 2.4.0-alpha6">
+<issues format="4" by="lint 3.0.0-alpha5">
 
     <issue
         id="MissingPermission"
@@ -14,28 +14,6 @@
 
     <issue
         id="MissingSuperCall"
-        message="Overriding method should call `super.onAnimationEnd`"
-        errorLine1="                public void onAnimationEnd(Animation animation) {"
-        errorLine2="                            ~~~~~~~~~~~~~~">
-        <location
-            file="java/android/support/v4/app/FragmentManager.java"
-            line="1604"
-            column="29"/>
-    </issue>
-
-    <issue
-        id="MissingSuperCall"
-        message="Overriding method should call `super.onAnimationEnd`"
-        errorLine1="        public void onAnimationEnd(Animation animation) {"
-        errorLine2="                    ~~~~~~~~~~~~~~">
-        <location
-            file="java/android/support/v4/app/FragmentManager.java"
-            line="3935"
-            column="21"/>
-    </issue>
-
-    <issue
-        id="MissingSuperCall"
         message="Overriding method should call `super.draw`"
         errorLine1="    public void draw(Canvas canvas) {"
         errorLine2="                ~~~~">
@@ -84,39 +62,6 @@
 
     <issue
         id="ResourceType"
-        message="Expected resource of type string"
-        errorLine1="                        ? Float.parseFloat(res.getString(R.dimen.lb_browse_header_select_scale))"
-        errorLine2="                                                         ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
-        <location
-            file="src/android/support/v17/leanback/widget/FocusHighlightHelper.java"
-            line="276"
-            column="58"/>
-    </issue>
-
-    <issue
-        id="ResourceType"
-        message="Expected resource of type string"
-        errorLine1="                        Integer.parseInt(res.getString(R.dimen.lb_browse_header_select_duration));"
-        errorLine2="                                                       ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
-        <location
-            file="src/android/support/v17/leanback/widget/FocusHighlightHelper.java"
-            line="279"
-            column="56"/>
-    </issue>
-
-    <issue
-        id="ResourceType"
-        message="Expected resource of type anim or interpolator"
-        errorLine1="                R.animator.lb_decelerator_4));"
-        errorLine2="                ~~~~~~~~~~~~~~~~~~~~~~~~~~~">
-        <location
-            file="kitkat/android/support/v17/leanback/transition/LeanbackTransitionHelperKitKat.java"
-            line="38"
-            column="17"/>
-    </issue>
-
-    <issue
-        id="ResourceType"
         message="Expected resource of type layout"
         errorLine1="            parser = mContext.getResources().getLayout(menuRes);"
         errorLine2="                                                       ~~~~~~~">
@@ -127,17 +72,6 @@
     </issue>
 
     <issue
-        id="ResourceType"
-        message="Expected resource of type xml"
-        errorLine1="            final XmlPullParser parser = res.getXml(resId);"
-        errorLine2="                                                    ~~~~~">
-        <location
-            file="src/android/support/graphics/drawable/VectorDrawableCompat.java"
-            line="557"
-            column="53"/>
-    </issue>
-
-    <issue
         id="Range"
         message="Value must be ≥ 0 (was -2147483648)"
         errorLine1="                                MeasureSpec.makeMeasureSpec(largestChildHeight,"
@@ -160,72 +94,6 @@
     </issue>
 
     <issue
-        id="Range"
-        message="Value must be ≥ 0 (was -1)"
-        errorLine1="                childHeightSpec = MeasureSpec.makeMeasureSpec(maxLayoutHeight, MeasureSpec.AT_MOST);"
-        errorLine2="                                                              ~~~~~~~~~~~~~~~">
-        <location
-            file="java/android/support/v4/widget/SlidingPaneLayout.java"
-            line="536"
-            column="63"/>
-    </issue>
-
-    <issue
-        id="Range"
-        message="Value must be ≥ 0 (was -1)"
-        errorLine1="                childHeightSpec = MeasureSpec.makeMeasureSpec(maxLayoutHeight, MeasureSpec.EXACTLY);"
-        errorLine2="                                                              ~~~~~~~~~~~~~~~">
-        <location
-            file="java/android/support/v4/widget/SlidingPaneLayout.java"
-            line="538"
-            column="63"/>
-    </issue>
-
-    <issue
-        id="Range"
-        message="Value must be ≥ 0 (was -1)"
-        errorLine1="                                childHeightSpec = MeasureSpec.makeMeasureSpec(maxLayoutHeight,"
-        errorLine2="                                                                              ~~~~~~~~~~~~~~~">
-        <location
-            file="java/android/support/v4/widget/SlidingPaneLayout.java"
-            line="586"
-            column="79"/>
-    </issue>
-
-    <issue
-        id="Range"
-        message="Value must be ≥ 0 (was -1)"
-        errorLine1="                                childHeightSpec = MeasureSpec.makeMeasureSpec(maxLayoutHeight,"
-        errorLine2="                                                                              ~~~~~~~~~~~~~~~">
-        <location
-            file="java/android/support/v4/widget/SlidingPaneLayout.java"
-            line="589"
-            column="79"/>
-    </issue>
-
-    <issue
-        id="Range"
-        message="Value must be ≥ 0 (was -1)"
-        errorLine1="                            childHeightSpec = MeasureSpec.makeMeasureSpec(maxLayoutHeight,"
-        errorLine2="                                                                          ~~~~~~~~~~~~~~~">
-        <location
-            file="java/android/support/v4/widget/SlidingPaneLayout.java"
-            line="608"
-            column="75"/>
-    </issue>
-
-    <issue
-        id="Range"
-        message="Value must be ≥ 0 (was -1)"
-        errorLine1="                            childHeightSpec = MeasureSpec.makeMeasureSpec(maxLayoutHeight,"
-        errorLine2="                                                                          ~~~~~~~~~~~~~~~">
-        <location
-            file="java/android/support/v4/widget/SlidingPaneLayout.java"
-            line="611"
-            column="75"/>
-    </issue>
-
-    <issue
         id="Suspicious0dp"
         message="Suspicious size: this will make the view invisible, should be used with `layout_weight`"
         errorLine1="        &lt;Space android:layout_width=&quot;0dp&quot; android:layout_height=&quot;@dimen/lb_preference_item_text_space_top&quot; />"
@@ -424,681 +292,6 @@
     </issue>
 
     <issue
-        id="LongLogTag"
-        message="The logging tag can be at most 23 characters, was 27 (DetailsOverviewRowPresenter)"
-        errorLine1="                if (DEBUG) Log.v(TAG, &quot;onLayoutChange &quot; + v);"
-        errorLine2="                                 ~~~">
-        <location
-            file="src/android/support/v17/leanback/widget/DetailsOverviewRowPresenter.java"
-            line="178"
-            column="34"/>
-    </issue>
-
-    <issue
-        id="LongLogTag"
-        message="The logging tag can be at most 23 characters, was 27 (DetailsOverviewRowPresenter)"
-        errorLine1="                Log.v(TAG, &quot;checkFirstAndLast fromScroll &quot; + fromScroll"
-        errorLine2="                      ~~~">
-        <location
-            file="src/android/support/v17/leanback/widget/DetailsOverviewRowPresenter.java"
-            line="237"
-            column="23"/>
-    </issue>
-
-    <issue
-        id="LongLogTag"
-        message="The logging tag can be at most 23 characters, was 25 (FragmentStatePagerAdapter)"
-        errorLine1="        if (DEBUG) Log.v(TAG, &quot;Adding item #&quot; + position + &quot;: f=&quot; + fragment);"
-        errorLine2="                         ~~~">
-        <location
-            file="java/android/support/v4/app/FragmentStatePagerAdapter.java"
-            line="110"
-            column="26"/>
-    </issue>
-
-    <issue
-        id="LongLogTag"
-        message="The logging tag can be at most 23 characters, was 25 (FragmentStatePagerAdapter)"
-        errorLine1="        if (DEBUG) Log.v(TAG, &quot;Removing item #&quot; + position + &quot;: f=&quot; + object"
-        errorLine2="                         ~~~">
-        <location
-            file="java/android/support/v4/app/FragmentStatePagerAdapter.java"
-            line="135"
-            column="26"/>
-    </issue>
-
-    <issue
-        id="LongLogTag"
-        message="The logging tag can be at most 23 characters, was 25 (FragmentStatePagerAdapter)"
-        errorLine1="                        Log.w(TAG, &quot;Bad fragment at key &quot; + key);"
-        errorLine2="                              ~~~">
-        <location
-            file="java/android/support/v4/app/FragmentStatePagerAdapter.java"
-            line="224"
-            column="31"/>
-    </issue>
-
-    <issue
-        id="LongLogTag"
-        message="The logging tag can be at most 23 characters, was 36 (FullWidthDetailsOverviewRowPresenter)"
-        errorLine1="                if (DEBUG) Log.v(TAG, &quot;onLayoutChange &quot; + v);"
-        errorLine2="                                 ~~~">
-        <location
-            file="src/android/support/v17/leanback/widget/FullWidthDetailsOverviewRowPresenter.java"
-            line="236"
-            column="34"/>
-    </issue>
-
-    <issue
-        id="LongLogTag"
-        message="The logging tag can be at most 23 characters, was 36 (FullWidthDetailsOverviewRowPresenter)"
-        errorLine1="                Log.v(TAG, &quot;checkFirstAndLast fromScroll &quot; + fromScroll"
-        errorLine2="                      ~~~">
-        <location
-            file="src/android/support/v17/leanback/widget/FullWidthDetailsOverviewRowPresenter.java"
-            line="295"
-            column="23"/>
-    </issue>
-
-    <issue
-        id="LongLogTag"
-        message="The logging tag can be at most 23 characters, was 25 (GuidedStepSupportFragment)"
-        errorLine1="            Log.w(TAG, &quot;Fragment is already exists, likely calling &quot;"
-        errorLine2="                  ~~~">
-        <location
-            file="src/android/support/v17/leanback/app/GuidedStepSupportFragment.java"
-            line="641"
-            column="19"/>
-    </issue>
-
-    <issue
-        id="LongLogTag"
-        message="The logging tag can be at most 23 characters, was 25 (GuidedStepSupportFragment)"
-        errorLine1="        if (DEBUG) Log.v(TAG, &quot;onCreate&quot;);"
-        errorLine2="                         ~~~">
-        <location
-            file="src/android/support/v17/leanback/app/GuidedStepSupportFragment.java"
-            line="999"
-            column="26"/>
-    </issue>
-
-    <issue
-        id="LongLogTag"
-        message="The logging tag can be at most 23 characters, was 25 (GuidedStepSupportFragment)"
-        errorLine1="        if (DEBUG) Log.v(TAG, &quot;onCreateView&quot;);"
-        errorLine2="                         ~~~">
-        <location
-            file="src/android/support/v17/leanback/app/GuidedStepSupportFragment.java"
-            line="1038"
-            column="26"/>
-    </issue>
-
-    <issue
-        id="LongLogTag"
-        message="The logging tag can be at most 23 characters, was 25 (GuidedStepSupportFragment)"
-        errorLine1="        if (DEBUG) Log.v(TAG, &quot;Found guided step theme flag? &quot; + found);"
-        errorLine2="                         ~~~">
-        <location
-            file="src/android/support/v17/leanback/app/GuidedStepSupportFragment.java"
-            line="1237"
-            column="26"/>
-    </issue>
-
-    <issue
-        id="LongLogTag"
-        message="The logging tag can be at most 23 characters, was 25 (GuidedStepSupportFragment)"
-        errorLine1="            if (DEBUG) Log.v(TAG, &quot;Found guided step theme reference? &quot; + found);"
-        errorLine2="                             ~~~">
-        <location
-            file="src/android/support/v17/leanback/app/GuidedStepSupportFragment.java"
-            line="1350"
-            column="30"/>
-    </issue>
-
-    <issue
-        id="LongLogTag"
-        message="The logging tag can be at most 23 characters, was 25 (GuidedStepSupportFragment)"
-        errorLine1="                Log.e(TAG, &quot;GuidedStepSupportFragment does not have an appropriate theme set.&quot;);"
-        errorLine2="                      ~~~">
-        <location
-            file="src/android/support/v17/leanback/app/GuidedStepSupportFragment.java"
-            line="1362"
-            column="23"/>
-    </issue>
-
-    <issue
-        id="LongLogTag"
-        message="The logging tag can be at most 23 characters, was 25 (OnboardingSupportFragment)"
-        errorLine1="            if (DEBUG) Log.v(TAG, &quot;Found onboarding theme reference? &quot; + found);"
-        errorLine2="                             ~~~">
-        <location
-            file="src/android/support/v17/leanback/app/OnboardingSupportFragment.java"
-            line="552"
-            column="30"/>
-    </issue>
-
-    <issue
-        id="LongLogTag"
-        message="The logging tag can be at most 23 characters, was 30 (PlaybackOverlaySupportFragment)"
-        errorLine1="            if (DEBUG) Log.v(TAG, &quot;onAnimationEnd &quot; + mBgAlpha);"
-        errorLine2="                             ~~~">
-        <location
-            file="src/android/support/v17/leanback/app/PlaybackOverlaySupportFragment.java"
-            line="148"
-            column="30"/>
-    </issue>
-
-    <issue
-        id="LongLogTag"
-        message="The logging tag can be at most 23 characters, was 30 (PlaybackOverlaySupportFragment)"
-        errorLine1="        if (DEBUG) Log.v(TAG, &quot;setFadingEnabled &quot; + enabled);"
-        errorLine2="                         ~~~">
-        <location
-            file="src/android/support/v17/leanback/app/PlaybackOverlaySupportFragment.java"
-            line="236"
-            column="26"/>
-    </issue>
-
-    <issue
-        id="LongLogTag"
-        message="The logging tag can be at most 23 characters, was 30 (PlaybackOverlaySupportFragment)"
-        errorLine1="        if (DEBUG) Log.v(TAG, &quot;tickle enabled &quot; + mFadingEnabled + &quot; isResumed &quot; + isResumed());"
-        errorLine2="                         ~~~">
-        <location
-            file="src/android/support/v17/leanback/app/PlaybackOverlaySupportFragment.java"
-            line="313"
-            column="26"/>
-    </issue>
-
-    <issue
-        id="LongLogTag"
-        message="The logging tag can be at most 23 characters, was 30 (PlaybackOverlaySupportFragment)"
-        errorLine1="        if (DEBUG) Log.v(TAG, &quot;onInterceptInputEvent hidden &quot; + controlsHidden + &quot; &quot; + event);"
-        errorLine2="                         ~~~">
-        <location
-            file="src/android/support/v17/leanback/app/PlaybackOverlaySupportFragment.java"
-            line="363"
-            column="26"/>
-    </issue>
-
-    <issue
-        id="LongLogTag"
-        message="The logging tag can be at most 23 characters, was 30 (PlaybackOverlaySupportFragment)"
-        errorLine1="                    if (DEBUG) Log.v(TAG, &quot;fraction &quot; + fraction);"
-        errorLine2="                                     ~~~">
-        <location
-            file="src/android/support/v17/leanback/app/PlaybackOverlaySupportFragment.java"
-            line="481"
-            column="38"/>
-    </issue>
-
-    <issue
-        id="LongLogTag"
-        message="The logging tag can be at most 23 characters, was 30 (PlaybackOverlaySupportFragment)"
-        errorLine1="        if (DEBUG) Log.v(TAG, &quot;fade &quot; + fadeIn);"
-        errorLine2="                         ~~~">
-        <location
-            file="src/android/support/v17/leanback/app/PlaybackOverlaySupportFragment.java"
-            line="577"
-            column="26"/>
-    </issue>
-
-    <issue
-        id="LongLogTag"
-        message="The logging tag can be at most 23 characters, was 30 (PlaybackOverlaySupportFragment)"
-        errorLine1="            if (DEBUG) Log.v(TAG, &quot;requested fade in progress&quot;);"
-        errorLine2="                             ~~~">
-        <location
-            file="src/android/support/v17/leanback/app/PlaybackOverlaySupportFragment.java"
-            line="582"
-            column="30"/>
-    </issue>
-
-    <issue
-        id="LongLogTag"
-        message="The logging tag can be at most 23 characters, was 30 (PlaybackOverlaySupportFragment)"
-        errorLine1="            if (DEBUG) Log.v(TAG, &quot;fade is no-op&quot;);"
-        errorLine2="                             ~~~">
-        <location
-            file="src/android/support/v17/leanback/app/PlaybackOverlaySupportFragment.java"
-            line="586"
-            column="30"/>
-    </issue>
-
-    <issue
-        id="LongLogTag"
-        message="The logging tag can be at most 23 characters, was 30 (PlaybackOverlaySupportFragment)"
-        errorLine1="            if (DEBUG) Log.v(TAG, &quot;onAttachedToWindow &quot; + vh.getViewHolder().view);"
-        errorLine2="                             ~~~">
-        <location
-            file="src/android/support/v17/leanback/app/PlaybackOverlaySupportFragment.java"
-            line="770"
-            column="30"/>
-    </issue>
-
-    <issue
-        id="LongLogTag"
-        message="The logging tag can be at most 23 characters, was 30 (PlaybackOverlaySupportFragment)"
-        errorLine1="                if (DEBUG) Log.v(TAG, &quot;setting alpha to 0&quot;);"
-        errorLine2="                                 ~~~">
-        <location
-            file="src/android/support/v17/leanback/app/PlaybackOverlaySupportFragment.java"
-            line="772"
-            column="34"/>
-    </issue>
-
-    <issue
-        id="LongLogTag"
-        message="The logging tag can be at most 23 characters, was 30 (PlaybackOverlaySupportFragment)"
-        errorLine1="            if (DEBUG) Log.v(TAG, &quot;onDetachedFromWindow &quot; + vh.getViewHolder().view);"
-        errorLine2="                             ~~~">
-        <location
-            file="src/android/support/v17/leanback/app/PlaybackOverlaySupportFragment.java"
-            line="781"
-            column="30"/>
-    </issue>
-
-    <issue
-        id="LongLogTag"
-        message="The logging tag can be at most 23 characters, was 28 (RoundedBitmapDrawableFactory)"
-        errorLine1="            Log.w(TAG, &quot;RoundedBitmapDrawable cannot decode &quot; + filepath);"
-        errorLine2="                  ~~~">
-        <location
-            file="java/android/support/v4/graphics/drawable/RoundedBitmapDrawableFactory.java"
-            line="80"
-            column="19"/>
-    </issue>
-
-    <issue
-        id="LongLogTag"
-        message="The logging tag can be at most 23 characters, was 28 (RoundedBitmapDrawableFactory)"
-        errorLine1="            Log.w(TAG, &quot;RoundedBitmapDrawable cannot decode &quot; + is);"
-        errorLine2="                  ~~~">
-        <location
-            file="java/android/support/v4/graphics/drawable/RoundedBitmapDrawableFactory.java"
-            line="93"
-            column="19"/>
-    </issue>
-
-    <issue
-        id="LongLogTag"
-        message="The logging tag can be at most 23 characters, was 26 (StaggeredGridLayoutManager)"
-        errorLine1="            Log.d(TAG, &quot;found saved state: &quot; + mPendingSavedState);"
-        errorLine2="                  ~~~">
-        <location
-            file="src/android/support/v7/widget/StaggeredGridLayoutManager.java"
-            line="783"
-            column="19"/>
-    </issue>
-
-    <issue
-        id="LongLogTag"
-        message="The logging tag can be at most 23 characters, was 26 (StaggeredGridLayoutManager)"
-        errorLine1="            Log.d(TAG, &quot;Deciding anchor info from fresh state&quot;);"
-        errorLine2="                  ~~~">
-        <location
-            file="src/android/support/v7/widget/StaggeredGridLayoutManager.java"
-            line="828"
-            column="19"/>
-    </issue>
-
-    <issue
-        id="LongLogTag"
-        message="The logging tag can be at most 23 characters, was 26 (StaggeredGridLayoutManager)"
-        errorLine1="            Log.d(TAG, &quot;invalid saved state class&quot;);"
-        errorLine2="                  ~~~">
-        <location
-            file="src/android/support/v7/widget/StaggeredGridLayoutManager.java"
-            line="1187"
-            column="19"/>
-    </issue>
-
-    <issue
-        id="LongLogTag"
-        message="The logging tag can be at most 23 characters, was 26 (StaggeredGridLayoutManager)"
-        errorLine1="            Log.d(TAG, &quot;saved state:\n&quot; + state);"
-        errorLine2="                  ~~~">
-        <location
-            file="src/android/support/v7/widget/StaggeredGridLayoutManager.java"
-            line="1236"
-            column="19"/>
-    </issue>
-
-    <issue
-        id="LongLogTag"
-        message="The logging tag can be at most 23 characters, was 26 (StaggeredGridLayoutManager)"
-        errorLine1="            Log.d(TAG, &quot;FILLING targetLine: &quot; + targetLine + &quot;,&quot;"
-        errorLine2="                  ~~~">
-        <location
-            file="src/android/support/v7/widget/StaggeredGridLayoutManager.java"
-            line="1559"
-            column="19"/>
-    </issue>
-
-    <issue
-        id="LongLogTag"
-        message="The logging tag can be at most 23 characters, was 26 (StaggeredGridLayoutManager)"
-        errorLine1="                    Log.d(TAG, &quot;assigned &quot; + currentSpan.mIndex + &quot; for &quot; + position);"
-        errorLine2="                          ~~~">
-        <location
-            file="src/android/support/v7/widget/StaggeredGridLayoutManager.java"
-            line="1580"
-            column="27"/>
-    </issue>
-
-    <issue
-        id="LongLogTag"
-        message="The logging tag can be at most 23 characters, was 26 (StaggeredGridLayoutManager)"
-        errorLine1="                    Log.d(TAG, &quot;using &quot; + spanIndex + &quot; for pos &quot; + position);"
-        errorLine2="                          ~~~">
-        <location
-            file="src/android/support/v7/widget/StaggeredGridLayoutManager.java"
-            line="1584"
-            column="27"/>
-    </issue>
-
-    <issue
-        id="LongLogTag"
-        message="The logging tag can be at most 23 characters, was 26 (StaggeredGridLayoutManager)"
-        errorLine1="            Log.d(TAG, &quot;asked &quot; + dt + &quot; scrolled&quot; + totalScroll);"
-        errorLine2="                  ~~~">
-        <location
-            file="src/android/support/v7/widget/StaggeredGridLayoutManager.java"
-            line="2153"
-            column="19"/>
-    </issue>
-
-    <issue
-        id="LongLogTag"
-        message="The logging tag can be at most 23 characters, was 26 (StaggeredGridLayoutManager)"
-        errorLine1="                    Log.d(TAG, &quot;Unknown focus request:&quot; + focusDirection);"
-        errorLine2="                          ~~~">
-        <location
-            file="src/android/support/v7/widget/StaggeredGridLayoutManager.java"
-            line="2385"
-            column="27"/>
-    </issue>
-
-    <issue
-        id="LongLogTag"
-        message="The logging tag can be at most 23 characters, was 27 (VerticalGridSupportFragment)"
-        errorLine1="            if (DEBUG) Log.v(TAG, &quot;grid selected position &quot; + position);"
-        errorLine2="                             ~~~">
-        <location
-            file="src/android/support/v17/leanback/app/VerticalGridSupportFragment.java"
-            line="120"
-            column="30"/>
-    </issue>
-
-    <issue
-        id="LongLogTag"
-        message="The logging tag can be at most 23 characters, was 24 (WakefulBroadcastReceiver)"
-        errorLine1="            Log.w(&quot;WakefulBroadcastReceiver&quot;, &quot;No active wake lock id #&quot; + id);"
-        errorLine2="                  ~~~~~~~~~~~~~~~~~~~~~~~~~~">
-        <location
-            file="java/android/support/v4/content/WakefulBroadcastReceiver.java"
-            line="141"
-            column="19"/>
-    </issue>
-
-    <issue
-        id="AppCompatCustomView"
-        message="This custom view should extend `android.support.v7.widget.AppCompatAutoCompleteTextView` instead"
-        errorLine1="public class AppCompatAutoCompleteTextView extends AutoCompleteTextView implements"
-        errorLine2="                                                   ~~~~~~~~~~~~~~~~~~~~">
-        <location
-            file="src/android/support/v7/widget/AppCompatAutoCompleteTextView.java"
-            line="49"
-            column="52"/>
-    </issue>
-
-    <issue
-        id="AppCompatCustomView"
-        message="This custom view should extend `android.support.v7.widget.AppCompatButton` instead"
-        errorLine1="public class AppCompatButton extends Button implements TintableBackgroundView {"
-        errorLine2="                                     ~~~~~~">
-        <location
-            file="src/android/support/v7/widget/AppCompatButton.java"
-            line="51"
-            column="38"/>
-    </issue>
-
-    <issue
-        id="AppCompatCustomView"
-        message="This custom view should extend `android.support.v7.widget.AppCompatCheckBox` instead"
-        errorLine1="public class AppCompatCheckBox extends CheckBox implements TintableCompoundButton {"
-        errorLine2="                                       ~~~~~~~~">
-        <location
-            file="src/android/support/v7/widget/AppCompatCheckBox.java"
-            line="49"
-            column="40"/>
-    </issue>
-
-    <issue
-        id="AppCompatCustomView"
-        message="This custom view should extend `android.support.v7.widget.AppCompatCheckedTextView` instead"
-        errorLine1="public class AppCompatCheckedTextView extends CheckedTextView {"
-        errorLine2="                                              ~~~~~~~~~~~~~~~">
-        <location
-            file="src/android/support/v7/widget/AppCompatCheckedTextView.java"
-            line="33"
-            column="47"/>
-    </issue>
-
-    <issue
-        id="AppCompatCustomView"
-        message="This custom view should extend `android.support.v7.widget.AppCompatEditText` instead"
-        errorLine1="public class AppCompatEditText extends EditText implements TintableBackgroundView {"
-        errorLine2="                                       ~~~~~~~~">
-        <location
-            file="src/android/support/v7/widget/AppCompatEditText.java"
-            line="48"
-            column="40"/>
-    </issue>
-
-    <issue
-        id="AppCompatCustomView"
-        message="This custom view should extend `android.support.v7.widget.AppCompatImageButton` instead"
-        errorLine1="public class AppCompatImageButton extends ImageButton implements TintableBackgroundView,"
-        errorLine2="                                          ~~~~~~~~~~~">
-        <location
-            file="src/android/support/v7/widget/AppCompatImageButton.java"
-            line="58"
-            column="43"/>
-    </issue>
-
-    <issue
-        id="AppCompatCustomView"
-        message="This custom view should extend `android.support.v7.widget.AppCompatImageView` instead"
-        errorLine1="public class AppCompatImageView extends ImageView implements TintableBackgroundView,"
-        errorLine2="                                        ~~~~~~~~~">
-        <location
-            file="src/android/support/v7/widget/AppCompatImageView.java"
-            line="57"
-            column="41"/>
-    </issue>
-
-    <issue
-        id="AppCompatCustomView"
-        message="This custom view should extend `android.support.v7.widget.AppCompatMultiAutoCompleteTextView` instead"
-        errorLine1="public class AppCompatMultiAutoCompleteTextView extends MultiAutoCompleteTextView"
-        errorLine2="                                                        ~~~~~~~~~~~~~~~~~~~~~~~~~">
-        <location
-            file="src/android/support/v7/widget/AppCompatMultiAutoCompleteTextView.java"
-            line="49"
-            column="57"/>
-    </issue>
-
-    <issue
-        id="AppCompatCustomView"
-        message="This custom view should extend `android.support.v7.widget.AppCompatRadioButton` instead"
-        errorLine1="public class AppCompatRadioButton extends RadioButton implements TintableCompoundButton {"
-        errorLine2="                                          ~~~~~~~~~~~">
-        <location
-            file="src/android/support/v7/widget/AppCompatRadioButton.java"
-            line="49"
-            column="43"/>
-    </issue>
-
-    <issue
-        id="AppCompatCustomView"
-        message="This custom view should extend `android.support.v7.widget.AppCompatRatingBar` instead"
-        errorLine1="public class AppCompatRatingBar extends RatingBar {"
-        errorLine2="                                        ~~~~~~~~~">
-        <location
-            file="src/android/support/v7/widget/AppCompatRatingBar.java"
-            line="34"
-            column="41"/>
-    </issue>
-
-    <issue
-        id="AppCompatCustomView"
-        message="This custom view should extend `android.support.v7.widget.AppCompatSeekBar` instead"
-        errorLine1="public class AppCompatSeekBar extends SeekBar {"
-        errorLine2="                                      ~~~~~~~">
-        <location
-            file="src/android/support/v7/widget/AppCompatSeekBar.java"
-            line="34"
-            column="39"/>
-    </issue>
-
-    <issue
-        id="AppCompatCustomView"
-        message="This custom view should extend `android.support.v7.widget.AppCompatSpinner` instead"
-        errorLine1="public class AppCompatSpinner extends Spinner implements TintableBackgroundView {"
-        errorLine2="                                      ~~~~~~~">
-        <location
-            file="src/android/support/v7/widget/AppCompatSpinner.java"
-            line="68"
-            column="39"/>
-    </issue>
-
-    <issue
-        id="AppCompatCustomView"
-        message="This custom view should extend `android.support.v7.widget.AppCompatTextView` instead"
-        errorLine1="public class AppCompatTextView extends TextView implements TintableBackgroundView,"
-        errorLine2="                                       ~~~~~~~~">
-        <location
-            file="src/android/support/v7/widget/AppCompatTextView.java"
-            line="60"
-            column="40"/>
-    </issue>
-
-    <issue
-        id="AppCompatCustomView"
-        message="This custom view should extend `android.support.v7.widget.AppCompatImageView` instead"
-        errorLine1="class CheckableImageView extends ImageView implements Checkable {"
-        errorLine2="                                 ~~~~~~~~~">
-        <location
-            file="src/android/support/v17/leanback/widget/CheckableImageView.java"
-            line="24"
-            column="34"/>
-    </issue>
-
-    <issue
-        id="AppCompatCustomView"
-        message="This custom view should extend `android.support.v7.widget.AppCompatImageView` instead"
-        errorLine1="class CircleImageView extends ImageView {"
-        errorLine2="                              ~~~~~~~~~">
-        <location
-            file="java/android/support/v4/widget/CircleImageView.java"
-            line="38"
-            column="31"/>
-    </issue>
-
-    <issue
-        id="AppCompatCustomView"
-        message="This custom view should extend `android.support.v7.widget.AppCompatTextView` instead"
-        errorLine1="public class DialogTitle extends TextView {"
-        errorLine2="                                 ~~~~~~~~">
-        <location
-            file="src/android/support/v7/widget/DialogTitle.java"
-            line="37"
-            column="34"/>
-    </issue>
-
-    <issue
-        id="AppCompatCustomView"
-        message="This custom view should extend `android.support.v7.widget.AppCompatEditText` instead"
-        errorLine1="public class GuidedActionEditText extends EditText implements ImeKeyMonitor {"
-        errorLine2="                                          ~~~~~~~~">
-        <location
-            file="src/android/support/v17/leanback/widget/GuidedActionEditText.java"
-            line="31"
-            column="43"/>
-    </issue>
-
-    <issue
-        id="AppCompatCustomView"
-        message="This custom view should extend `android.support.v7.widget.AppCompatImageView` instead"
-        errorLine1="public class PreferenceImageView extends ImageView {"
-        errorLine2="                                         ~~~~~~~~~">
-        <location
-            file="src/android/support/v7/internal/widget/PreferenceImageView.java"
-            line="33"
-            column="42"/>
-    </issue>
-
-    <issue
-        id="AppCompatCustomView"
-        message="This custom view should extend `android.support.v7.widget.AppCompatTextView` instead"
-        errorLine1="class ResizingTextView extends TextView {"
-        errorLine2="                               ~~~~~~~~">
-        <location
-            file="src/android/support/v17/leanback/widget/ResizingTextView.java"
-            line="28"
-            column="32"/>
-    </issue>
-
-    <issue
-        id="AppCompatCustomView"
-        message="This custom view should extend `android.support.v7.widget.AppCompatTextView` instead"
-        errorLine1="public final class RowHeaderView extends TextView {"
-        errorLine2="                                         ~~~~~~~~">
-        <location
-            file="src/android/support/v17/leanback/widget/RowHeaderView.java"
-            line="24"
-            column="42"/>
-    </issue>
-
-    <issue
-        id="AppCompatCustomView"
-        message="This custom view should extend `android.support.v7.widget.AppCompatEditText` instead"
-        errorLine1="class StreamingTextView extends EditText {"
-        errorLine2="                                ~~~~~~~~">
-        <location
-            file="src/android/support/v17/leanback/widget/StreamingTextView.java"
-            line="43"
-            column="33"/>
-    </issue>
-
-    <issue
-        id="UniqueConstants"
-        message="Constants `FLAG_CVE_EQ_PVE` and `FLAG_CVE_EQ_PVE` specify the same exact value (8192); this is usually a cut &amp; paste or merge error"
-        errorLine1="            FLAG_CVE_EQ_PVE, FLAG_CVE_EQ_PVE, FLAG_CVE_LT_PVE"
-        errorLine2="                             ~~~~~~~~~~~~~~~">
-        <location
-            file="src/android/support/v7/widget/ViewBoundsCheck.java"
-            line="125"
-            column="30"/>
-        <location
-            file="src/android/support/v7/widget/ViewBoundsCheck.java"
-            line="125"
-            column="13"/>
-    </issue>
-
-    <issue
-        id="Orientation"
-        message="No orientation specified, and the default is horizontal. This is a common source of bugs when children are added dynamically."
-        errorLine1="            &lt;LinearLayout"
-        errorLine2="            ^">
-        <location
-            file="res/layout/lb_row_media_item.xml"
-            line="63"
-            column="13"/>
-    </issue>
-
-    <issue
         id="WrongConstant"
         message="Must be one of: PixelFormat.UNKNOWN, PixelFormat.TRANSLUCENT, PixelFormat.TRANSPARENT, PixelFormat.OPAQUE"
         errorLine1="        return 0;"
@@ -1111,101 +304,13 @@
 
     <issue
         id="WrongConstant"
-        message="Must be one of: ViewCompat.LAYOUT_DIRECTION_LTR, ViewCompat.LAYOUT_DIRECTION_RTL"
-        errorLine1="            boolean isRtl = ViewCompat.getLayoutDirection(focused) == View.LAYOUT_DIRECTION_RTL;"
-        errorLine2="                                                                      ~~~~~~~~~~~~~~~~~~~~~~~~~">
-        <location
-            file="src/android/support/v17/leanback/app/BrowseFragment.java"
-            line="1029"
-            column="71"/>
-    </issue>
-
-    <issue
-        id="WrongConstant"
-        message="Must be one of: ViewCompat.LAYOUT_DIRECTION_LTR, ViewCompat.LAYOUT_DIRECTION_RTL"
-        errorLine1="            boolean isRtl = ViewCompat.getLayoutDirection(focused) == View.LAYOUT_DIRECTION_RTL;"
-        errorLine2="                                                                      ~~~~~~~~~~~~~~~~~~~~~~~~~">
-        <location
-            file="src/android/support/v17/leanback/app/BrowseSupportFragment.java"
-            line="1032"
-            column="71"/>
-    </issue>
-
-    <issue
-        id="WrongConstant"
-        message="Must be one of: Gravity.LEFT, Gravity.RIGHT, GravityCompat.START, GravityCompat.END"
-        errorLine1="        setDrawerLockMode(lockMode, gravity);"
-        errorLine2="                                    ~~~~~~~">
+        message="Must be one or more of: Gravity.LEFT, Gravity.RIGHT, GravityCompat.START, GravityCompat.END"
+        errorLine1="                            + gravityToString(childGravity) + &quot; but this &quot; + TAG + &quot; already has a &quot;"
+        errorLine2="                                              ~~~~~~~~~~~~">
         <location
             file="java/android/support/v4/widget/DrawerLayout.java"
-            line="680"
-            column="37"/>
-    </issue>
-
-    <issue
-        id="WrongConstant"
-        message="Must be one of: Gravity.LEFT, Gravity.RIGHT, GravityCompat.START, GravityCompat.END"
-        errorLine1="        return getDrawerLockMode(drawerGravity);"
-        errorLine2="                                 ~~~~~~~~~~~~~">
-        <location
-            file="java/android/support/v4/widget/DrawerLayout.java"
-            line="753"
-            column="34"/>
-    </issue>
-
-    <issue
-        id="WrongConstant"
-        message="Flag not allowed here"
-        errorLine1="                        getDrawerViewAbsoluteGravity(child) &amp; Gravity.HORIZONTAL_GRAVITY_MASK;"
-        errorLine2="                        ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
-        <location
-            file="java/android/support/v4/widget/DrawerLayout.java"
-            line="1086"
-            column="25"/>
-    </issue>
-
-    <issue
-        id="WrongConstant"
-        message="Must be one of: View.VISIBLE, View.INVISIBLE, View.GONE"
-        errorLine1="                    v.setVisibility(mChildVisibility);"
-        errorLine2="                                    ~~~~~~~~~~~~~~~~">
-        <location
-            file="src/android/support/v17/leanback/widget/GridLayoutManager.java"
-            line="1566"
-            column="37"/>
-    </issue>
-
-    <issue
-        id="WrongConstant"
-        message="Must be one of: View.VISIBLE, View.INVISIBLE, View.GONE"
-        errorLine1="                getChildAt(i).setVisibility(mChildVisibility);"
-        errorLine2="                                            ~~~~~~~~~~~~~~~~">
-        <location
-            file="src/android/support/v17/leanback/widget/GridLayoutManager.java"
-            line="3468"
-            column="45"/>
-    </issue>
-
-    <issue
-        id="WrongConstant"
-        message="Must be one of: ViewCompat.LAYOUT_DIRECTION_LTR, ViewCompat.LAYOUT_DIRECTION_RTL"
-        errorLine1="        boolean isRtl = ViewCompat.getLayoutDirection(view) == View.LAYOUT_DIRECTION_RTL;"
-        errorLine2="                                                               ~~~~~~~~~~~~~~~~~~~~~~~~~">
-        <location
-            file="src/android/support/v17/leanback/widget/HorizontalHoverCardSwitcher.java"
-            line="50"
-            column="64"/>
-    </issue>
-
-    <issue
-        id="WrongConstant"
-        message="Must be one of: ViewCompat.LAYOUT_DIRECTION_LTR, ViewCompat.LAYOUT_DIRECTION_RTL"
-        errorLine1="                    == View.LAYOUT_DIRECTION_RTL;"
-        errorLine2="                       ~~~~~~~~~~~~~~~~~~~~~~~~~">
-        <location
-            file="src/android/support/v17/leanback/widget/TitleHelper.java"
-            line="49"
-            column="24"/>
+            line="1075"
+            column="47"/>
     </issue>
 
     <issue
@@ -1231,17 +336,6 @@
     </issue>
 
     <issue
-        id="WrongConstant"
-        message="Must be one of: View.LAYOUT_DIRECTION_LTR, View.LAYOUT_DIRECTION_RTL"
-        errorLine1="            return isAutoMirrored() &amp;&amp; getLayoutDirection() == LayoutDirection.RTL;"
-        errorLine2="                                                               ~~~~~~~~~~~~~~~~~~~">
-        <location
-            file="src/android/support/graphics/drawable/VectorDrawableCompat.java"
-            line="821"
-            column="64"/>
-    </issue>
-
-    <issue
         id="RtlCompat"
         message="Inconsistent alignment specification between `textAlignment` and `gravity` attributes: was `center_vertical`, expected `start`"
         errorLine1="            android:textAlignment=&quot;viewStart&quot; />"
@@ -1256,19 +350,4 @@
             column="37"/>
     </issue>
 
-    <issue
-        id="RtlCompat"
-        message="Inconsistent alignment specification between `textAlignment` and `gravity` attributes: was `center_vertical|end`, expected `start`"
-        errorLine1="                    android:textAlignment=&quot;viewStart&quot;"
-        errorLine2="                                           ~~~~~~~~~">
-        <location
-            file="res/layout/lb_search_bar.xml"
-            line="70"
-            column="44"/>
-        <location
-            file="res/layout/lb_search_bar.xml"
-            line="57"
-            column="45"/>
-    </issue>
-
 </issues>
diff --git a/v4/lint-baseline.xml b/v4/lint-baseline.xml
index 2a79d28..b373b5e 100644
--- a/v4/lint-baseline.xml
+++ b/v4/lint-baseline.xml
@@ -1,191 +1,15 @@
 <?xml version="1.0" encoding="UTF-8"?>
-<issues format="4" by="lint 2.4.0-alpha6">
-
-    <issue
-        id="MissingSuperCall"
-        message="Overriding method should call `super.onAnimationEnd`"
-        errorLine1="                public void onAnimationEnd(Animation animation) {"
-        errorLine2="                            ~~~~~~~~~~~~~~">
-        <location
-            file="java/android/support/v4/app/FragmentManager.java"
-            line="1604"
-            column="29"/>
-    </issue>
-
-    <issue
-        id="MissingSuperCall"
-        message="Overriding method should call `super.onAnimationEnd`"
-        errorLine1="        public void onAnimationEnd(Animation animation) {"
-        errorLine2="                    ~~~~~~~~~~~~~~">
-        <location
-            file="java/android/support/v4/app/FragmentManager.java"
-            line="3935"
-            column="21"/>
-    </issue>
-
-    <issue
-        id="Range"
-        message="Value must be ≥ 0 (was -1)"
-        errorLine1="                childHeightSpec = MeasureSpec.makeMeasureSpec(maxLayoutHeight, MeasureSpec.AT_MOST);"
-        errorLine2="                                                              ~~~~~~~~~~~~~~~">
-        <location
-            file="java/android/support/v4/widget/SlidingPaneLayout.java"
-            line="536"
-            column="63"/>
-    </issue>
-
-    <issue
-        id="Range"
-        message="Value must be ≥ 0 (was -1)"
-        errorLine1="                childHeightSpec = MeasureSpec.makeMeasureSpec(maxLayoutHeight, MeasureSpec.EXACTLY);"
-        errorLine2="                                                              ~~~~~~~~~~~~~~~">
-        <location
-            file="java/android/support/v4/widget/SlidingPaneLayout.java"
-            line="538"
-            column="63"/>
-    </issue>
-
-    <issue
-        id="Range"
-        message="Value must be ≥ 0 (was -1)"
-        errorLine1="                                childHeightSpec = MeasureSpec.makeMeasureSpec(maxLayoutHeight,"
-        errorLine2="                                                                              ~~~~~~~~~~~~~~~">
-        <location
-            file="java/android/support/v4/widget/SlidingPaneLayout.java"
-            line="586"
-            column="79"/>
-    </issue>
-
-    <issue
-        id="Range"
-        message="Value must be ≥ 0 (was -1)"
-        errorLine1="                                childHeightSpec = MeasureSpec.makeMeasureSpec(maxLayoutHeight,"
-        errorLine2="                                                                              ~~~~~~~~~~~~~~~">
-        <location
-            file="java/android/support/v4/widget/SlidingPaneLayout.java"
-            line="589"
-            column="79"/>
-    </issue>
-
-    <issue
-        id="Range"
-        message="Value must be ≥ 0 (was -1)"
-        errorLine1="                            childHeightSpec = MeasureSpec.makeMeasureSpec(maxLayoutHeight,"
-        errorLine2="                                                                          ~~~~~~~~~~~~~~~">
-        <location
-            file="java/android/support/v4/widget/SlidingPaneLayout.java"
-            line="608"
-            column="75"/>
-    </issue>
-
-    <issue
-        id="Range"
-        message="Value must be ≥ 0 (was -1)"
-        errorLine1="                            childHeightSpec = MeasureSpec.makeMeasureSpec(maxLayoutHeight,"
-        errorLine2="                                                                          ~~~~~~~~~~~~~~~">
-        <location
-            file="java/android/support/v4/widget/SlidingPaneLayout.java"
-            line="611"
-            column="75"/>
-    </issue>
-
-    <issue
-        id="LongLogTag"
-        message="The logging tag can be at most 23 characters, was 25 (FragmentStatePagerAdapter)"
-        errorLine1="        if (DEBUG) Log.v(TAG, &quot;Adding item #&quot; + position + &quot;: f=&quot; + fragment);"
-        errorLine2="                         ~~~">
-        <location
-            file="java/android/support/v4/app/FragmentStatePagerAdapter.java"
-            line="110"
-            column="26"/>
-    </issue>
-
-    <issue
-        id="LongLogTag"
-        message="The logging tag can be at most 23 characters, was 25 (FragmentStatePagerAdapter)"
-        errorLine1="        if (DEBUG) Log.v(TAG, &quot;Removing item #&quot; + position + &quot;: f=&quot; + object"
-        errorLine2="                         ~~~">
-        <location
-            file="java/android/support/v4/app/FragmentStatePagerAdapter.java"
-            line="135"
-            column="26"/>
-    </issue>
-
-    <issue
-        id="LongLogTag"
-        message="The logging tag can be at most 23 characters, was 25 (FragmentStatePagerAdapter)"
-        errorLine1="                        Log.w(TAG, &quot;Bad fragment at key &quot; + key);"
-        errorLine2="                              ~~~">
-        <location
-            file="java/android/support/v4/app/FragmentStatePagerAdapter.java"
-            line="224"
-            column="31"/>
-    </issue>
-
-    <issue
-        id="LongLogTag"
-        message="The logging tag can be at most 23 characters, was 28 (RoundedBitmapDrawableFactory)"
-        errorLine1="            Log.w(TAG, &quot;RoundedBitmapDrawable cannot decode &quot; + filepath);"
-        errorLine2="                  ~~~">
-        <location
-            file="java/android/support/v4/graphics/drawable/RoundedBitmapDrawableFactory.java"
-            line="80"
-            column="19"/>
-    </issue>
-
-    <issue
-        id="LongLogTag"
-        message="The logging tag can be at most 23 characters, was 28 (RoundedBitmapDrawableFactory)"
-        errorLine1="            Log.w(TAG, &quot;RoundedBitmapDrawable cannot decode &quot; + is);"
-        errorLine2="                  ~~~">
-        <location
-            file="java/android/support/v4/graphics/drawable/RoundedBitmapDrawableFactory.java"
-            line="93"
-            column="19"/>
-    </issue>
-
-    <issue
-        id="LongLogTag"
-        message="The logging tag can be at most 23 characters, was 24 (WakefulBroadcastReceiver)"
-        errorLine1="            Log.w(&quot;WakefulBroadcastReceiver&quot;, &quot;No active wake lock id #&quot; + id);"
-        errorLine2="                  ~~~~~~~~~~~~~~~~~~~~~~~~~~">
-        <location
-            file="java/android/support/v4/content/WakefulBroadcastReceiver.java"
-            line="141"
-            column="19"/>
-    </issue>
+<issues format="4" by="lint 3.0.0-alpha5">
 
     <issue
         id="WrongConstant"
-        message="Must be one of: Gravity.LEFT, Gravity.RIGHT, GravityCompat.START, GravityCompat.END"
-        errorLine1="        setDrawerLockMode(lockMode, gravity);"
-        errorLine2="                                    ~~~~~~~">
+        message="Must be one or more of: Gravity.LEFT, Gravity.RIGHT, GravityCompat.START, GravityCompat.END"
+        errorLine1="                            + gravityToString(childGravity) + &quot; but this &quot; + TAG + &quot; already has a &quot;"
+        errorLine2="                                              ~~~~~~~~~~~~">
         <location
             file="java/android/support/v4/widget/DrawerLayout.java"
-            line="680"
-            column="37"/>
-    </issue>
-
-    <issue
-        id="WrongConstant"
-        message="Must be one of: Gravity.LEFT, Gravity.RIGHT, GravityCompat.START, GravityCompat.END"
-        errorLine1="        return getDrawerLockMode(drawerGravity);"
-        errorLine2="                                 ~~~~~~~~~~~~~">
-        <location
-            file="java/android/support/v4/widget/DrawerLayout.java"
-            line="753"
-            column="34"/>
-    </issue>
-
-    <issue
-        id="WrongConstant"
-        message="Flag not allowed here"
-        errorLine1="                        getDrawerViewAbsoluteGravity(child) &amp; Gravity.HORIZONTAL_GRAVITY_MASK;"
-        errorLine2="                        ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
-        <location
-            file="java/android/support/v4/widget/DrawerLayout.java"
-            line="1086"
-            column="25"/>
+            line="1075"
+            column="47"/>
     </issue>
 
 </issues>
diff --git a/v7/appcompat/lint-baseline.xml b/v7/appcompat/lint-baseline.xml
index 9436020..d533bd0 100644
--- a/v7/appcompat/lint-baseline.xml
+++ b/v7/appcompat/lint-baseline.xml
@@ -1,5 +1,5 @@
 <?xml version="1.0" encoding="UTF-8"?>
-<issues format="4" by="lint 2.4.0-alpha6">
+<issues format="4" by="lint 3.0.0-alpha5">
 
     <issue
         id="MissingPermission"
@@ -14,28 +14,6 @@
 
     <issue
         id="MissingSuperCall"
-        message="Overriding method should call `super.onAnimationEnd`"
-        errorLine1="                public void onAnimationEnd(Animation animation) {"
-        errorLine2="                            ~~~~~~~~~~~~~~">
-        <location
-            file="java/android/support/v4/app/FragmentManager.java"
-            line="1604"
-            column="29"/>
-    </issue>
-
-    <issue
-        id="MissingSuperCall"
-        message="Overriding method should call `super.onAnimationEnd`"
-        errorLine1="        public void onAnimationEnd(Animation animation) {"
-        errorLine2="                    ~~~~~~~~~~~~~~">
-        <location
-            file="java/android/support/v4/app/FragmentManager.java"
-            line="3935"
-            column="21"/>
-    </issue>
-
-    <issue
-        id="MissingSuperCall"
         message="Overriding method should call `super.draw`"
         errorLine1="    public void draw(Canvas canvas) {"
         errorLine2="                ~~~~">
@@ -94,17 +72,6 @@
     </issue>
 
     <issue
-        id="ResourceType"
-        message="Expected resource of type xml"
-        errorLine1="            final XmlPullParser parser = res.getXml(resId);"
-        errorLine2="                                                    ~~~~~">
-        <location
-            file="src/android/support/graphics/drawable/VectorDrawableCompat.java"
-            line="557"
-            column="53"/>
-    </issue>
-
-    <issue
         id="Range"
         message="Value must be ≥ 0 (was -2147483648)"
         errorLine1="                                MeasureSpec.makeMeasureSpec(largestChildHeight,"
@@ -127,72 +94,6 @@
     </issue>
 
     <issue
-        id="Range"
-        message="Value must be ≥ 0 (was -1)"
-        errorLine1="                childHeightSpec = MeasureSpec.makeMeasureSpec(maxLayoutHeight, MeasureSpec.AT_MOST);"
-        errorLine2="                                                              ~~~~~~~~~~~~~~~">
-        <location
-            file="java/android/support/v4/widget/SlidingPaneLayout.java"
-            line="536"
-            column="63"/>
-    </issue>
-
-    <issue
-        id="Range"
-        message="Value must be ≥ 0 (was -1)"
-        errorLine1="                childHeightSpec = MeasureSpec.makeMeasureSpec(maxLayoutHeight, MeasureSpec.EXACTLY);"
-        errorLine2="                                                              ~~~~~~~~~~~~~~~">
-        <location
-            file="java/android/support/v4/widget/SlidingPaneLayout.java"
-            line="538"
-            column="63"/>
-    </issue>
-
-    <issue
-        id="Range"
-        message="Value must be ≥ 0 (was -1)"
-        errorLine1="                                childHeightSpec = MeasureSpec.makeMeasureSpec(maxLayoutHeight,"
-        errorLine2="                                                                              ~~~~~~~~~~~~~~~">
-        <location
-            file="java/android/support/v4/widget/SlidingPaneLayout.java"
-            line="586"
-            column="79"/>
-    </issue>
-
-    <issue
-        id="Range"
-        message="Value must be ≥ 0 (was -1)"
-        errorLine1="                                childHeightSpec = MeasureSpec.makeMeasureSpec(maxLayoutHeight,"
-        errorLine2="                                                                              ~~~~~~~~~~~~~~~">
-        <location
-            file="java/android/support/v4/widget/SlidingPaneLayout.java"
-            line="589"
-            column="79"/>
-    </issue>
-
-    <issue
-        id="Range"
-        message="Value must be ≥ 0 (was -1)"
-        errorLine1="                            childHeightSpec = MeasureSpec.makeMeasureSpec(maxLayoutHeight,"
-        errorLine2="                                                                          ~~~~~~~~~~~~~~~">
-        <location
-            file="java/android/support/v4/widget/SlidingPaneLayout.java"
-            line="608"
-            column="75"/>
-    </issue>
-
-    <issue
-        id="Range"
-        message="Value must be ≥ 0 (was -1)"
-        errorLine1="                            childHeightSpec = MeasureSpec.makeMeasureSpec(maxLayoutHeight,"
-        errorLine2="                                                                          ~~~~~~~~~~~~~~~">
-        <location
-            file="java/android/support/v4/widget/SlidingPaneLayout.java"
-            line="611"
-            column="75"/>
-    </issue>
-
-    <issue
         id="LongLogTag"
         message="The logging tag can be at most 23 characters, was 30 (ActionBarDrawerToggleHoneycomb)"
         errorLine1="                Log.w(TAG, &quot;Couldn&apos;t set home-as-up indicator via JB-MR2 API&quot;, e);"
@@ -292,72 +193,6 @@
     </issue>
 
     <issue
-        id="LongLogTag"
-        message="The logging tag can be at most 23 characters, was 25 (FragmentStatePagerAdapter)"
-        errorLine1="        if (DEBUG) Log.v(TAG, &quot;Adding item #&quot; + position + &quot;: f=&quot; + fragment);"
-        errorLine2="                         ~~~">
-        <location
-            file="java/android/support/v4/app/FragmentStatePagerAdapter.java"
-            line="110"
-            column="26"/>
-    </issue>
-
-    <issue
-        id="LongLogTag"
-        message="The logging tag can be at most 23 characters, was 25 (FragmentStatePagerAdapter)"
-        errorLine1="        if (DEBUG) Log.v(TAG, &quot;Removing item #&quot; + position + &quot;: f=&quot; + object"
-        errorLine2="                         ~~~">
-        <location
-            file="java/android/support/v4/app/FragmentStatePagerAdapter.java"
-            line="135"
-            column="26"/>
-    </issue>
-
-    <issue
-        id="LongLogTag"
-        message="The logging tag can be at most 23 characters, was 25 (FragmentStatePagerAdapter)"
-        errorLine1="                        Log.w(TAG, &quot;Bad fragment at key &quot; + key);"
-        errorLine2="                              ~~~">
-        <location
-            file="java/android/support/v4/app/FragmentStatePagerAdapter.java"
-            line="224"
-            column="31"/>
-    </issue>
-
-    <issue
-        id="LongLogTag"
-        message="The logging tag can be at most 23 characters, was 28 (RoundedBitmapDrawableFactory)"
-        errorLine1="            Log.w(TAG, &quot;RoundedBitmapDrawable cannot decode &quot; + filepath);"
-        errorLine2="                  ~~~">
-        <location
-            file="java/android/support/v4/graphics/drawable/RoundedBitmapDrawableFactory.java"
-            line="80"
-            column="19"/>
-    </issue>
-
-    <issue
-        id="LongLogTag"
-        message="The logging tag can be at most 23 characters, was 28 (RoundedBitmapDrawableFactory)"
-        errorLine1="            Log.w(TAG, &quot;RoundedBitmapDrawable cannot decode &quot; + is);"
-        errorLine2="                  ~~~">
-        <location
-            file="java/android/support/v4/graphics/drawable/RoundedBitmapDrawableFactory.java"
-            line="93"
-            column="19"/>
-    </issue>
-
-    <issue
-        id="LongLogTag"
-        message="The logging tag can be at most 23 characters, was 24 (WakefulBroadcastReceiver)"
-        errorLine1="            Log.w(&quot;WakefulBroadcastReceiver&quot;, &quot;No active wake lock id #&quot; + id);"
-        errorLine2="                  ~~~~~~~~~~~~~~~~~~~~~~~~~~">
-        <location
-            file="java/android/support/v4/content/WakefulBroadcastReceiver.java"
-            line="141"
-            column="19"/>
-    </issue>
-
-    <issue
         id="WrongConstant"
         message="Must be one of: PixelFormat.UNKNOWN, PixelFormat.TRANSLUCENT, PixelFormat.TRANSPARENT, PixelFormat.OPAQUE"
         errorLine1="        return 0;"
@@ -370,35 +205,13 @@
 
     <issue
         id="WrongConstant"
-        message="Must be one of: Gravity.LEFT, Gravity.RIGHT, GravityCompat.START, GravityCompat.END"
-        errorLine1="        setDrawerLockMode(lockMode, gravity);"
-        errorLine2="                                    ~~~~~~~">
+        message="Must be one or more of: Gravity.LEFT, Gravity.RIGHT, GravityCompat.START, GravityCompat.END"
+        errorLine1="                            + gravityToString(childGravity) + &quot; but this &quot; + TAG + &quot; already has a &quot;"
+        errorLine2="                                              ~~~~~~~~~~~~">
         <location
             file="java/android/support/v4/widget/DrawerLayout.java"
-            line="680"
-            column="37"/>
-    </issue>
-
-    <issue
-        id="WrongConstant"
-        message="Must be one of: Gravity.LEFT, Gravity.RIGHT, GravityCompat.START, GravityCompat.END"
-        errorLine1="        return getDrawerLockMode(drawerGravity);"
-        errorLine2="                                 ~~~~~~~~~~~~~">
-        <location
-            file="java/android/support/v4/widget/DrawerLayout.java"
-            line="753"
-            column="34"/>
-    </issue>
-
-    <issue
-        id="WrongConstant"
-        message="Flag not allowed here"
-        errorLine1="                        getDrawerViewAbsoluteGravity(child) &amp; Gravity.HORIZONTAL_GRAVITY_MASK;"
-        errorLine2="                        ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
-        <location
-            file="java/android/support/v4/widget/DrawerLayout.java"
-            line="1086"
-            column="25"/>
+            line="1075"
+            column="47"/>
     </issue>
 
     <issue
@@ -423,15 +236,4 @@
             column="36"/>
     </issue>
 
-    <issue
-        id="WrongConstant"
-        message="Must be one of: View.LAYOUT_DIRECTION_LTR, View.LAYOUT_DIRECTION_RTL"
-        errorLine1="            return isAutoMirrored() &amp;&amp; getLayoutDirection() == LayoutDirection.RTL;"
-        errorLine2="                                                               ~~~~~~~~~~~~~~~~~~~">
-        <location
-            file="src/android/support/graphics/drawable/VectorDrawableCompat.java"
-            line="821"
-            column="64"/>
-    </issue>
-
 </issues>
diff --git a/v7/appcompat/src/android/support/v7/app/NotificationCompat.java b/v7/appcompat/src/android/support/v7/app/NotificationCompat.java
index 47c819f..9d9cc82 100644
--- a/v7/appcompat/src/android/support/v7/app/NotificationCompat.java
+++ b/v7/appcompat/src/android/support/v7/app/NotificationCompat.java
@@ -21,8 +21,7 @@
 import android.app.Notification;
 import android.app.PendingIntent;
 import android.content.Context;
-import android.content.res.ColorStateList;
-import android.graphics.Color;
+import android.media.session.MediaSession;
 import android.os.Build;
 import android.os.Bundle;
 import android.os.IBinder;
@@ -32,22 +31,12 @@
 import android.support.v4.app.BundleCompat;
 import android.support.v4.app.NotificationBuilderWithBuilderAccessor;
 import android.support.v4.media.session.MediaSessionCompat;
-import android.support.v4.text.BidiFormatter;
 import android.support.v7.appcompat.R;
-import android.text.SpannableStringBuilder;
-import android.text.Spanned;
-import android.text.TextUtils;
-import android.text.style.TextAppearanceSpan;
+import android.view.View;
 import android.widget.RemoteViews;
 
-import java.util.List;
-
 /**
- * An extension of {@link android.support.v4.app.NotificationCompat} which supports
- * {@link android.support.v7.app.NotificationCompat.MediaStyle},
- * {@link android.support.v7.app.NotificationCompat.DecoratedCustomViewStyle},
- * and {@link android.support.v7.app.NotificationCompat.DecoratedMediaCustomViewStyle}.
- * You should start using this variant if you need support any of these styles.
+ * An extension of {@link android.support.v4.app.NotificationCompat} which adds additional styles.
  */
 public class NotificationCompat extends android.support.v4.app.NotificationCompat {
 
@@ -83,387 +72,22 @@
         return null;
     }
 
-    @RequiresApi(24)
-    private static void addStyleToBuilderApi24(NotificationBuilderWithBuilderAccessor builder,
-            android.support.v4.app.NotificationCompat.Builder b) {
-        if (b.mStyle instanceof DecoratedCustomViewStyle) {
-            NotificationCompatImpl24.addDecoratedCustomViewStyle(builder);
-        } else if (b.mStyle instanceof DecoratedMediaCustomViewStyle) {
-            DecoratedMediaCustomViewStyle mediaStyle = (DecoratedMediaCustomViewStyle) b.mStyle;
-            NotificationCompatImpl24.addDecoratedMediaCustomViewStyle(builder,
-                    mediaStyle.mActionsToShowInCompact,
-                    mediaStyle.mToken != null ? mediaStyle.mToken.getToken() : null);
-        } else if (!(b.mStyle instanceof MessagingStyle)) {
-            addStyleGetContentViewLollipop(builder, b);
-        }
-    }
-
-    @RequiresApi(21)
-    private static RemoteViews addStyleGetContentViewLollipop(
-            NotificationBuilderWithBuilderAccessor builder,
-            android.support.v4.app.NotificationCompat.Builder b) {
-        if (b.mStyle instanceof MediaStyle) {
-            MediaStyle mediaStyle = (MediaStyle) b.mStyle;
-            NotificationCompatImpl21.addMediaStyle(builder,
-                    mediaStyle.mActionsToShowInCompact,
-                    mediaStyle.mToken != null ? mediaStyle.mToken.getToken() : null);
-
-            boolean hasContentView = b.getContentView() != null;
-            // If we are on L/M the media notification will only be colored if the expanded version
-            // is of media style, so we have to create a custom view for the collapsed version as
-            // well in that case.
-            boolean isMorL = Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP
-                    && Build.VERSION.SDK_INT <= Build.VERSION_CODES.M;
-            boolean createCustomContent = hasContentView
-                    || (isMorL && b.getBigContentView() != null);
-            if (b.mStyle instanceof DecoratedMediaCustomViewStyle && createCustomContent) {
-                RemoteViews contentViewMedia = NotificationCompatImplBase.overrideContentViewMedia(
-                        builder, b.mContext, b.mContentTitle, b.mContentText, b.mContentInfo,
-                        b.mNumber, b.mLargeIcon, b.mSubText, b.mUseChronometer,
-                        b.getWhenIfShowing(), b.getPriority(), b.mActions,
-                        mediaStyle.mActionsToShowInCompact, false /* no cancel button on L */,
-                        null /* cancelButtonIntent */, hasContentView /* isDecoratedCustomView */);
-                if (hasContentView) {
-                    NotificationCompatImplBase.buildIntoRemoteViews(b.mContext, contentViewMedia,
-                            b.getContentView());
-                }
-                setBackgroundColor(b.mContext, contentViewMedia, b.getColor());
-                return contentViewMedia;
-            }
-            return null;
-        } else if (b.mStyle instanceof DecoratedCustomViewStyle) {
-            return getDecoratedContentView(b);
-        }
-        return addStyleGetContentViewJellybean(builder, b);
-    }
-
-    @RequiresApi(16)
-    private static RemoteViews addStyleGetContentViewJellybean(
-            NotificationBuilderWithBuilderAccessor builder,
-            android.support.v4.app.NotificationCompat.Builder b) {
-        if (b.mStyle instanceof MessagingStyle) {
-            addMessagingFallBackStyle((MessagingStyle) b.mStyle, builder, b);
-        }
-        return addStyleGetContentViewIcs(builder, b);
-    }
-
-    private static CharSequence makeMessageLine(android.support.v4.app.NotificationCompat.Builder b,
-            MessagingStyle style,
-            MessagingStyle.Message m) {
-        BidiFormatter bidi = BidiFormatter.getInstance();
-        SpannableStringBuilder sb = new SpannableStringBuilder();
-        boolean afterLollipop = Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP;
-        int color = afterLollipop || Build.VERSION.SDK_INT <= Build.VERSION_CODES.GINGERBREAD_MR1
-                ? Color.BLACK : Color.WHITE;
-        CharSequence replyName = m.getSender();
-        if (TextUtils.isEmpty(m.getSender())) {
-            replyName = style.getUserDisplayName() == null
-                    ? "" : style.getUserDisplayName();
-            color = afterLollipop && b.getColor() != NotificationCompat.COLOR_DEFAULT
-                    ? b.getColor()
-                    : color;
-        }
-        CharSequence senderText = bidi.unicodeWrap(replyName);
-        sb.append(senderText);
-        sb.setSpan(makeFontColorSpan(color),
-                sb.length() - senderText.length(),
-                sb.length(),
-                Spanned.SPAN_EXCLUSIVE_EXCLUSIVE /* flags */);
-        CharSequence text = m.getText() == null ? "" : m.getText();
-        sb.append("  ").append(bidi.unicodeWrap(text));
-        return sb;
-    }
-
-    private static TextAppearanceSpan makeFontColorSpan(int color) {
-        return new TextAppearanceSpan(null, 0, 0, ColorStateList.valueOf(color), null);
-    }
-
-    @RequiresApi(16)
-    private static void addMessagingFallBackStyle(MessagingStyle style,
-            NotificationBuilderWithBuilderAccessor builder,
-            android.support.v4.app.NotificationCompat.Builder b) {
-        SpannableStringBuilder completeMessage = new SpannableStringBuilder();
-        List<MessagingStyle.Message> messages = style.getMessages();
-        boolean showNames = style.getConversationTitle() != null
-                || hasMessagesWithoutSender(style.getMessages());
-        for (int i = messages.size() - 1; i >= 0; i--) {
-            MessagingStyle.Message m = messages.get(i);
-            CharSequence line;
-            line = showNames ? makeMessageLine(b, style, m) : m.getText();
-            if (i != messages.size() - 1) {
-                completeMessage.insert(0, "\n");
-            }
-            completeMessage.insert(0, line);
-        }
-        NotificationCompatImplJellybean.addBigTextStyle(builder, completeMessage);
-    }
-
-    private static boolean hasMessagesWithoutSender(
-            List<MessagingStyle.Message> messages) {
-        for (int i = messages.size() - 1; i >= 0; i--) {
-            MessagingStyle.Message m = messages.get(i);
-            if (m.getSender() == null) {
-                return true;
-            }
-        }
-        return false;
-    }
-
-    @RequiresApi(14)
-    private static RemoteViews addStyleGetContentViewIcs(
-            NotificationBuilderWithBuilderAccessor builder,
-            android.support.v4.app.NotificationCompat.Builder b) {
-        if (b.mStyle instanceof MediaStyle) {
-            MediaStyle mediaStyle = (MediaStyle) b.mStyle;
-            boolean isDecorated = b.mStyle instanceof DecoratedMediaCustomViewStyle
-                    && b.getContentView() != null;
-            RemoteViews contentViewMedia = NotificationCompatImplBase.overrideContentViewMedia(
-                    builder, b.mContext, b.mContentTitle, b.mContentText, b.mContentInfo, b.mNumber,
-                    b.mLargeIcon, b.mSubText, b.mUseChronometer, b.getWhenIfShowing(),
-                    b.getPriority(), b.mActions, mediaStyle.mActionsToShowInCompact,
-                    mediaStyle.mShowCancelButton, mediaStyle.mCancelButtonIntent, isDecorated);
-            if (isDecorated) {
-                NotificationCompatImplBase.buildIntoRemoteViews(b.mContext, contentViewMedia,
-                        b.getContentView());
-                return contentViewMedia;
-            }
-        } else if (b.mStyle instanceof DecoratedCustomViewStyle) {
-            return getDecoratedContentView(b);
-        }
-        return null;
-    }
-
-    @RequiresApi(16)
-    private static void addBigStyleToBuilderJellybean(Notification n,
-            android.support.v4.app.NotificationCompat.Builder b) {
-        if (b.mStyle instanceof MediaStyle) {
-            MediaStyle mediaStyle = (MediaStyle) b.mStyle;
-            RemoteViews innerView = b.getBigContentView() != null
-                    ? b.getBigContentView()
-                    : b.getContentView();
-            boolean isDecorated = b.mStyle instanceof DecoratedMediaCustomViewStyle
-                    && innerView != null;
-            NotificationCompatImplBase.overrideMediaBigContentView(n, b.mContext,
-                    b.mContentTitle, b.mContentText, b.mContentInfo, b.mNumber, b.mLargeIcon,
-                    b.mSubText, b.mUseChronometer, b.getWhenIfShowing(), b.getPriority(), 0,
-                    b.mActions, mediaStyle.mShowCancelButton, mediaStyle.mCancelButtonIntent,
-                    isDecorated);
-            if (isDecorated) {
-                NotificationCompatImplBase.buildIntoRemoteViews(b.mContext, n.bigContentView,
-                        innerView);
-            }
-        } else if (b.mStyle instanceof DecoratedCustomViewStyle) {
-            addDecoratedBigStyleToBuilderJellybean(n, b);
-        }
-    }
-
-    private static RemoteViews getDecoratedContentView(
-            android.support.v4.app.NotificationCompat.Builder b) {
-        if (b.getContentView() == null) {
-            // No special content view
-            return null;
-        }
-        RemoteViews remoteViews = NotificationCompatImplBase.applyStandardTemplateWithActions(
-                b.mContext, b.mContentTitle, b.mContentText, b.mContentInfo, b.mNumber,
-                b.mNotification.icon, b.mLargeIcon, b.mSubText, b.mUseChronometer,
-                b.getWhenIfShowing(), b.getPriority(), b.getColor(),
-                R.layout.notification_template_custom_big, false /* fitIn1U */, null /* actions */);
-        NotificationCompatImplBase.buildIntoRemoteViews(b.mContext, remoteViews,
-                b.getContentView());
-        return remoteViews;
-    }
-
-    @RequiresApi(16)
-    private static void addDecoratedBigStyleToBuilderJellybean(Notification n,
-            android.support.v4.app.NotificationCompat.Builder b) {
-        RemoteViews bigContentView = b.getBigContentView();
-        RemoteViews innerView = bigContentView != null ? bigContentView : b.getContentView();
-        if (innerView == null) {
-            // No expandable notification
-            return;
-        }
-        RemoteViews remoteViews = NotificationCompatImplBase.applyStandardTemplateWithActions(
-                b.mContext, b.mContentTitle, b.mContentText, b.mContentInfo, b.mNumber,
-                n.icon ,b.mLargeIcon, b.mSubText, b.mUseChronometer, b.getWhenIfShowing(),
-                b.getPriority(), b.getColor(), R.layout.notification_template_custom_big,
-                false /* fitIn1U */, b.mActions);
-        NotificationCompatImplBase.buildIntoRemoteViews(b.mContext, remoteViews, innerView);
-        n.bigContentView = remoteViews;
-    }
-
-    @RequiresApi(21)
-    private static void addDecoratedHeadsUpToBuilderLollipop(Notification n,
-            android.support.v4.app.NotificationCompat.Builder b) {
-        RemoteViews headsUp = b.getHeadsUpContentView();
-        RemoteViews innerView = headsUp != null ? headsUp : b.getContentView();
-        if (headsUp == null) {
-            // No expandable notification
-            return;
-        }
-        RemoteViews remoteViews = NotificationCompatImplBase.applyStandardTemplateWithActions(
-                b.mContext, b.mContentTitle, b.mContentText, b.mContentInfo, b.mNumber, n.icon,
-                b.mLargeIcon, b.mSubText, b.mUseChronometer, b.getWhenIfShowing(), b.getPriority(),
-                b.getColor(), R.layout.notification_template_custom_big, false /* fitIn1U */,
-                b.mActions);
-        NotificationCompatImplBase.buildIntoRemoteViews(b.mContext, remoteViews, innerView);
-        n.headsUpContentView = remoteViews;
-    }
-
-    @RequiresApi(21)
-    private static void addBigStyleToBuilderLollipop(Notification n,
-            android.support.v4.app.NotificationCompat.Builder b) {
-        RemoteViews innerView = b.getBigContentView() != null
-                ? b.getBigContentView()
-                : b.getContentView();
-        if (b.mStyle instanceof DecoratedMediaCustomViewStyle && innerView != null) {
-            NotificationCompatImplBase.overrideMediaBigContentView(n, b.mContext,
-                    b.mContentTitle, b.mContentText, b.mContentInfo, b.mNumber, b.mLargeIcon,
-                    b.mSubText, b.mUseChronometer, b.getWhenIfShowing(), b.getPriority(), 0,
-                    b.mActions, false /* showCancelButton */, null /* cancelButtonIntent */,
-                    true /* decoratedCustomView */);
-                    NotificationCompatImplBase.buildIntoRemoteViews(b.mContext, n.bigContentView,
-                            innerView);
-            setBackgroundColor(b.mContext, n.bigContentView, b.getColor());
-        } else if (b.mStyle instanceof DecoratedCustomViewStyle) {
-            addDecoratedBigStyleToBuilderJellybean(n, b);
-        }
-    }
-
-    private static void setBackgroundColor(Context context, RemoteViews views, int color) {
-        if (color == COLOR_DEFAULT) {
-            color = context.getResources().getColor(
-                    R.color.notification_material_background_media_default_color);
-        }
-        views.setInt(R.id.status_bar_latest_event_content, "setBackgroundColor", color);
-    }
-
-    @RequiresApi(21)
-    private static void addHeadsUpToBuilderLollipop(Notification n,
-            android.support.v4.app.NotificationCompat.Builder b) {
-        RemoteViews innerView = b.getHeadsUpContentView() != null
-                ? b.getHeadsUpContentView()
-                : b.getContentView();
-        if (b.mStyle instanceof DecoratedMediaCustomViewStyle && innerView != null) {
-            n.headsUpContentView = NotificationCompatImplBase.generateMediaBigView(b.mContext,
-                    b.mContentTitle, b.mContentText, b.mContentInfo, b.mNumber,
-                    b.mLargeIcon, b.mSubText, b.mUseChronometer, b.getWhenIfShowing(),
-                    b.getPriority(), 0, b.mActions, false /* showCancelButton */,
-                    null /* cancelButtonIntent */, true /* decoratedCustomView */);
-            NotificationCompatImplBase.buildIntoRemoteViews(b.mContext, n.headsUpContentView,
-                    innerView);
-            setBackgroundColor(b.mContext, n.headsUpContentView, b.getColor());
-        } else if (b.mStyle instanceof DecoratedCustomViewStyle) {
-            addDecoratedHeadsUpToBuilderLollipop(n, b);
-        }
-    }
-
     /**
-     * See {@link android.support.v4.app.NotificationCompat}. In addition to the builder in v4, this
-     * builder also supports {@link MediaStyle}.
+     * @deprecated All {@link android.support.v4.app.NotificationCompat.Style styles} can now be
+     * used with {@link android.support.v4.app.NotificationCompat.Builder}.
      */
+    @Deprecated
     public static class Builder extends android.support.v4.app.NotificationCompat.Builder {
 
         /**
          * @inheritDoc
+         * @deprecated Use {@link android.support.v4.app.NotificationCompat.Builder
+         * #NotificationCompat.Builder(Context, String)}
          */
+        @Deprecated
         public Builder(Context context) {
             super(context);
         }
-
-        /**
-         * @hide
-         */
-        @RestrictTo(LIBRARY_GROUP)
-        @Override
-        protected BuilderExtender getExtender() {
-            if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
-                return new Api24Extender();
-            } else if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
-                return new LollipopExtender();
-            } else if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN) {
-                return new JellybeanExtender();
-            } else if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.ICE_CREAM_SANDWICH) {
-                return new IceCreamSandwichExtender();
-            } else {
-                return super.getExtender();
-            }
-        }
-    }
-
-    @RequiresApi(14)
-    private static class IceCreamSandwichExtender extends BuilderExtender {
-
-        IceCreamSandwichExtender() {
-        }
-
-        @Override
-        public Notification build(android.support.v4.app.NotificationCompat.Builder b,
-                NotificationBuilderWithBuilderAccessor builder) {
-            RemoteViews contentView = addStyleGetContentViewIcs(builder, b);
-            Notification n = builder.build();
-            // The above call might override decorated content views again, let's make sure it
-            // sticks.
-            if (contentView != null) {
-                n.contentView = contentView;
-            } else if (b.getContentView() != null) {
-                n.contentView = b.getContentView();
-            }
-            return n;
-        }
-    }
-
-    @RequiresApi(16)
-    private static class JellybeanExtender extends BuilderExtender {
-
-        JellybeanExtender() {
-        }
-
-        @Override
-        public Notification build(android.support.v4.app.NotificationCompat.Builder b,
-                NotificationBuilderWithBuilderAccessor builder) {
-            RemoteViews contentView = addStyleGetContentViewJellybean(builder, b);
-            Notification n = builder.build();
-            // The above call might override decorated content views again, let's make sure it
-            // sticks.
-            if (contentView != null) {
-                n.contentView = contentView;
-            }
-            addBigStyleToBuilderJellybean(n, b);
-            return n;
-        }
-    }
-
-    @RequiresApi(21)
-    private static class LollipopExtender extends BuilderExtender {
-
-        LollipopExtender() {
-        }
-
-        @Override
-        public Notification build(android.support.v4.app.NotificationCompat.Builder b,
-                NotificationBuilderWithBuilderAccessor builder) {
-            RemoteViews contentView = addStyleGetContentViewLollipop(builder, b);
-            Notification n = builder.build();
-            // The above call might override decorated content views again, let's make sure it
-            // sticks.
-            if (contentView != null) {
-                n.contentView = contentView;
-            }
-            addBigStyleToBuilderLollipop(n, b);
-            addHeadsUpToBuilderLollipop(n, b);
-            return n;
-        }
-    }
-
-    @RequiresApi(24)
-    private static class Api24Extender extends BuilderExtender {
-
-        @Override
-        public Notification build(android.support.v4.app.NotificationCompat.Builder b,
-                NotificationBuilderWithBuilderAccessor builder) {
-            addStyleToBuilderApi24(builder, b);
-            return builder.build();
-        }
     }
 
     /**
@@ -471,9 +95,11 @@
      *
      * In the expanded form, {@link Notification#bigContentView}, up to 5
      * {@link android.support.v4.app.NotificationCompat.Action}s specified with
-     * {@link NotificationCompat.Builder#addAction(int, CharSequence, PendingIntent) addAction} will
-     * be shown as icon-only pushbuttons, suitable for transport controls. The Bitmap given to
-     * {@link NotificationCompat.Builder#setLargeIcon(android.graphics.Bitmap) setLargeIcon()} will
+     * {@link android.support.v4.app.NotificationCompat.Builder#addAction(int, CharSequence,
+     * PendingIntent) addAction} will be shown as icon-only pushbuttons, suitable for transport
+     * controls. The Bitmap given to
+     * {@link android.support.v4.app.NotificationCompat.Builder#setLargeIcon(
+     * android.graphics.Bitmap) setLargeIcon()} will
      * be treated as album artwork.
      *
      * Unlike the other styles provided here, MediaStyle can also modify the standard-size
@@ -483,7 +109,8 @@
      *
      * Notifications created with MediaStyle will have their category set to
      * {@link Notification#CATEGORY_TRANSPORT CATEGORY_TRANSPORT} unless you set a different
-     * category using {@link NotificationCompat.Builder#setCategory(String) setCategory()}.
+     * category using {@link android.support.v4.app.NotificationCompat.Builder#setCategory(String)
+     * setCategory()}.
      *
      * Finally, if you attach a {@link android.media.session.MediaSession.Token} using
      * {@link android.support.v7.app.NotificationCompat.MediaStyle#setMediaSession}, the System UI
@@ -491,7 +118,7 @@
      * accordingly (by showing album artwork in the lockscreen, for example).
      *
      * To use this style with your Notification, feed it to
-     * {@link NotificationCompat.Builder#setStyle} like so:
+     * {@link android.support.v4.app.NotificationCompat.Builder#setStyle} like so:
      * <pre class="prettyprint">
      * Notification noti = new NotificationCompat.Builder()
      *     .setSmallIcon(R.drawable.ic_stat_player)
@@ -507,6 +134,9 @@
      */
     public static class MediaStyle extends android.support.v4.app.NotificationCompat.Style {
 
+        private static final int MAX_MEDIA_BUTTONS_IN_COMPACT = 3;
+        private static final int MAX_MEDIA_BUTTONS = 5;
+
         int[] mActionsToShowInCompact = null;
         MediaSessionCompat.Token mToken;
         boolean mShowCancelButton;
@@ -564,7 +194,9 @@
          * @param show whether to show a cancel button
          */
         public MediaStyle setShowCancelButton(boolean show) {
-            mShowCancelButton = show;
+            if (Build.VERSION.SDK_INT < 21) {
+                mShowCancelButton = show;
+            }
             return this;
         }
 
@@ -578,6 +210,142 @@
             mCancelButtonIntent = pendingIntent;
             return this;
         }
+
+        /**
+         * @hide
+         */
+        @RestrictTo(LIBRARY_GROUP)
+        @Override
+        public void apply(NotificationBuilderWithBuilderAccessor builder) {
+            if (Build.VERSION.SDK_INT >= 21) {
+                builder.getBuilder().setStyle(
+                        fillInMediaStyle(new Notification.MediaStyle()));
+            } else if (mShowCancelButton) {
+                builder.getBuilder().setOngoing(true);
+            }
+        }
+
+        @RequiresApi(21)
+        Notification.MediaStyle fillInMediaStyle(Notification.MediaStyle style) {
+            if (mActionsToShowInCompact != null) {
+                style.setShowActionsInCompactView(mActionsToShowInCompact);
+            }
+            if (mToken != null) {
+                style.setMediaSession((MediaSession.Token) mToken.getToken());
+            }
+            return style;
+        }
+
+        /**
+         * @hide
+         */
+        @RestrictTo(LIBRARY_GROUP)
+        @Override
+        public RemoteViews makeContentView(NotificationBuilderWithBuilderAccessor builder) {
+            if (Build.VERSION.SDK_INT >= 21) {
+                // No custom content view required
+                return null;
+            }
+            return generateContentView();
+        }
+
+        RemoteViews generateContentView() {
+            RemoteViews view = NotificationCompatImplBase.applyStandardTemplate(
+                    mBuilder, false /* showSmallIcon */,
+                    getContentViewLayoutResource(), true /* fitIn1U */);
+
+            final int numActions = mBuilder.mActions.size();
+            final int numActionsInCompact = mActionsToShowInCompact == null
+                    ? 0
+                    : Math.min(mActionsToShowInCompact.length, MAX_MEDIA_BUTTONS_IN_COMPACT);
+            view.removeAllViews(R.id.media_actions);
+            if (numActionsInCompact > 0) {
+                for (int i = 0; i < numActionsInCompact; i++) {
+                    if (i >= numActions) {
+                        throw new IllegalArgumentException(String.format(
+                                "setShowActionsInCompactView: action %d out of bounds (max %d)",
+                                i, numActions - 1));
+                    }
+
+                    final NotificationCompat.Action action =
+                            mBuilder.mActions.get(mActionsToShowInCompact[i]);
+                    final RemoteViews button = generateMediaActionButton(action);
+                    view.addView(R.id.media_actions, button);
+                }
+            }
+            if (mShowCancelButton) {
+                view.setViewVisibility(R.id.end_padder, View.GONE);
+                view.setViewVisibility(R.id.cancel_action, View.VISIBLE);
+                view.setOnClickPendingIntent(R.id.cancel_action, mCancelButtonIntent);
+                view.setInt(R.id.cancel_action, "setAlpha", mBuilder.mContext
+                        .getResources().getInteger(R.integer.cancel_button_image_alpha));
+            } else {
+                view.setViewVisibility(R.id.end_padder, View.VISIBLE);
+                view.setViewVisibility(R.id.cancel_action, View.GONE);
+            }
+            return view;
+        }
+
+        private RemoteViews generateMediaActionButton(NotificationCompat.Action action) {
+            final boolean tombstone = (action.getActionIntent() == null);
+            RemoteViews button = new RemoteViews(mBuilder.mContext.getPackageName(),
+                    R.layout.notification_media_action);
+            button.setImageViewResource(R.id.action0, action.getIcon());
+            if (!tombstone) {
+                button.setOnClickPendingIntent(R.id.action0, action.getActionIntent());
+            }
+            if (Build.VERSION.SDK_INT >= 15) {
+                button.setContentDescription(R.id.action0, action.getTitle());
+            }
+            return button;
+        }
+
+        int getContentViewLayoutResource() {
+            return R.layout.notification_template_media;
+        }
+
+        /**
+         * @hide
+         */
+        @RestrictTo(LIBRARY_GROUP)
+        @Override
+        public RemoteViews makeBigContentView(NotificationBuilderWithBuilderAccessor builder) {
+            if (Build.VERSION.SDK_INT >= 21) {
+                // No custom content view required
+                return null;
+            }
+            return generateBigContentView();
+        }
+
+        RemoteViews generateBigContentView() {
+            final int actionCount = Math.min(mBuilder.mActions.size(), MAX_MEDIA_BUTTONS);
+            RemoteViews big = NotificationCompatImplBase.applyStandardTemplate(
+                    mBuilder, false /* showSmallIcon */,
+                    getBigContentViewLayoutResource(actionCount), false /* fitIn1U */);
+
+            big.removeAllViews(R.id.media_actions);
+            if (actionCount > 0) {
+                for (int i = 0; i < actionCount; i++) {
+                    final RemoteViews button = generateMediaActionButton(mBuilder.mActions.get(i));
+                    big.addView(R.id.media_actions, button);
+                }
+            }
+            if (mShowCancelButton) {
+                big.setViewVisibility(R.id.cancel_action, View.VISIBLE);
+                big.setInt(R.id.cancel_action, "setAlpha", mBuilder.mContext
+                        .getResources().getInteger(R.integer.cancel_button_image_alpha));
+                big.setOnClickPendingIntent(R.id.cancel_action, mCancelButtonIntent);
+            } else {
+                big.setViewVisibility(R.id.cancel_action, View.GONE);
+            }
+            return big;
+        }
+
+        int getBigContentViewLayoutResource(int actionCount) {
+            return actionCount <= 3
+                    ? R.layout.notification_template_big_media_narrow
+                    : R.layout.notification_template_big_media;
+        }
     }
 
 
@@ -594,7 +362,7 @@
      * corresponding custom views to display.
      *
      * <p>To use this style with your Notification, feed it to
-     * {@link NotificationCompat.Builder#setStyle(Style)} like so:
+     * {@link android.support.v4.app.NotificationCompat.Builder#setStyle(Style)} like so:
      * <pre class="prettyprint">
      * Notification noti = new NotificationCompat.Builder()
      *     .setSmallIcon(R.drawable.ic_stat_player)
@@ -611,8 +379,121 @@
      */
     public static class DecoratedCustomViewStyle extends Style {
 
+        private static final int MAX_ACTION_BUTTONS = 3;
+
         public DecoratedCustomViewStyle() {
         }
+
+        /**
+         * @hide
+         */
+        @RestrictTo(LIBRARY_GROUP)
+        @Override
+        public void apply(NotificationBuilderWithBuilderAccessor builder) {
+            if (Build.VERSION.SDK_INT >= 24) {
+                builder.getBuilder().setStyle(new Notification.DecoratedCustomViewStyle());
+            }
+        }
+
+        /**
+         * @hide
+         */
+        @RestrictTo(LIBRARY_GROUP)
+        @Override
+        public RemoteViews makeContentView(NotificationBuilderWithBuilderAccessor builder) {
+            if (Build.VERSION.SDK_INT >= 24) {
+                // No custom content view required
+                return null;
+            }
+            if (mBuilder.getContentView() == null) {
+                // No special content view
+                return null;
+            }
+            return createRemoteViews(mBuilder.getContentView(), false);
+        }
+
+        /**
+         * @hide
+         */
+        @RestrictTo(LIBRARY_GROUP)
+        @Override
+        public RemoteViews makeBigContentView(NotificationBuilderWithBuilderAccessor builder) {
+            if (Build.VERSION.SDK_INT >= 24) {
+                // No custom big content view required
+                return null;
+            }
+            RemoteViews bigContentView = mBuilder.getBigContentView();
+            RemoteViews innerView = bigContentView != null
+                    ? bigContentView
+                    : mBuilder.getContentView();
+            if (innerView == null) {
+                // No expandable notification
+                return null;
+            }
+            return createRemoteViews(innerView, true);
+        }
+
+        /**
+         * @hide
+         */
+        @RestrictTo(LIBRARY_GROUP)
+        @Override
+        public RemoteViews makeHeadsUpContentView(NotificationBuilderWithBuilderAccessor builder) {
+            if (Build.VERSION.SDK_INT >= 24) {
+                // No custom heads up content view required
+                return null;
+            }
+            RemoteViews headsUp = mBuilder.getHeadsUpContentView();
+            RemoteViews innerView = headsUp != null ? headsUp : mBuilder.getContentView();
+            if (headsUp == null) {
+                // No expandable notification
+                return null;
+            }
+            return createRemoteViews(innerView, true);
+        }
+
+        private RemoteViews createRemoteViews(RemoteViews innerView, boolean showActions) {
+            RemoteViews remoteViews = NotificationCompatImplBase.applyStandardTemplate(
+                    mBuilder, true /* showSmallIcon */,
+                    R.layout.notification_template_custom_big, false /* fitIn1U */);
+            remoteViews.removeAllViews(R.id.actions);
+            boolean actionsVisible = false;
+            if (showActions && mBuilder.mActions != null) {
+                int numActions = Math.min(mBuilder.mActions.size(), MAX_ACTION_BUTTONS);
+                if (numActions > 0) {
+                    actionsVisible = true;
+                    for (int i = 0; i < numActions; i++) {
+                        final RemoteViews button = generateActionButton(mBuilder.mActions.get(i));
+                        remoteViews.addView(R.id.actions, button);
+                    }
+                }
+            }
+            int actionVisibility = actionsVisible ? View.VISIBLE : View.GONE;
+            remoteViews.setViewVisibility(R.id.actions, actionVisibility);
+            remoteViews.setViewVisibility(R.id.action_divider, actionVisibility);
+            NotificationCompatImplBase.buildIntoRemoteViews(mBuilder.mContext,
+                    remoteViews, innerView);
+            return remoteViews;
+        }
+
+        private RemoteViews generateActionButton(NotificationCompat.Action action) {
+            final boolean tombstone = (action.actionIntent == null);
+            RemoteViews button = new RemoteViews(mBuilder.mContext.getPackageName(),
+                    tombstone ? R.layout.notification_action_tombstone
+                            : R.layout.notification_action);
+            button.setImageViewBitmap(R.id.action_image,
+                    NotificationCompatImplBase.createColoredBitmap(mBuilder.mContext,
+                            action.getIcon(), mBuilder.mContext.getResources()
+                                    .getColor(R.color.notification_action_color_filter)));
+            button.setTextViewText(R.id.action_text, action.title);
+            if (!tombstone) {
+                button.setOnClickPendingIntent(R.id.action_container, action.actionIntent);
+            }
+            if (Build.VERSION.SDK_INT >= 15) {
+                button.setContentDescription(R.id.action_container, action.title);
+            }
+            return button;
+        }
     }
 
     /**
@@ -628,7 +509,7 @@
      * corresponding custom views to display.
      *
      * <p>To use this style with your Notification, feed it to
-     * {@link NotificationCompat.Builder#setStyle(Style)} like so:
+     * {@link android.support.v4.app.NotificationCompat.Builder#setStyle(Style)} like so:
      * <pre class="prettyprint">
      * Notification noti = new Notification.Builder()
      *     .setSmallIcon(R.drawable.ic_stat_player)
@@ -652,5 +533,134 @@
 
         public DecoratedMediaCustomViewStyle() {
         }
+
+        /**
+         * @hide
+         */
+        @RestrictTo(LIBRARY_GROUP)
+        @Override
+        public void apply(NotificationBuilderWithBuilderAccessor builder) {
+            if (Build.VERSION.SDK_INT >= 24) {
+                builder.getBuilder().setStyle(
+                        fillInMediaStyle(new Notification.DecoratedMediaCustomViewStyle()));
+            } else {
+                super.apply(builder);
+            }
+        }
+
+        /**
+         * @hide
+         */
+        @RestrictTo(LIBRARY_GROUP)
+        @Override
+        public RemoteViews makeContentView(NotificationBuilderWithBuilderAccessor builder) {
+            if (Build.VERSION.SDK_INT >= 24) {
+                // No custom content view required
+                return null;
+            }
+            boolean hasContentView = mBuilder.getContentView() != null;
+            if (Build.VERSION.SDK_INT >= 21) {
+                // If we are on L/M the media notification will only be colored if the expanded
+                // version is of media style, so we have to create a custom view for the collapsed
+                // version as well in that case.
+                boolean createCustomContent = hasContentView
+                        || mBuilder.getBigContentView() != null;
+                if (createCustomContent) {
+                    RemoteViews contentView = generateContentView();
+                    if (hasContentView) {
+                        NotificationCompatImplBase.buildIntoRemoteViews(mBuilder.mContext,
+                                contentView,
+                                mBuilder.getContentView());
+                    }
+                    setBackgroundColor(contentView);
+                    return contentView;
+                }
+            } else {
+                RemoteViews contentView = generateContentView();
+                if (hasContentView) {
+                    NotificationCompatImplBase.buildIntoRemoteViews(mBuilder.mContext,
+                            contentView,
+                            mBuilder.getContentView());
+                    return contentView;
+                }
+            }
+            return null;
+        }
+
+        @Override
+        int getContentViewLayoutResource() {
+            return mBuilder.getContentView() != null
+                    ? R.layout.notification_template_media_custom
+                    : super.getContentViewLayoutResource();
+        }
+
+        /**
+         * @hide
+         */
+        @RestrictTo(LIBRARY_GROUP)
+        @Override
+        public RemoteViews makeBigContentView(NotificationBuilderWithBuilderAccessor builder) {
+            if (Build.VERSION.SDK_INT >= 24) {
+                // No custom big content view required
+                return null;
+            }
+            RemoteViews innerView = mBuilder.getBigContentView() != null
+                    ? mBuilder.getBigContentView()
+                    : mBuilder.getContentView();
+            if (innerView == null) {
+                // No expandable notification
+                return null;
+            }
+            RemoteViews bigContentView = generateBigContentView();
+            NotificationCompatImplBase.buildIntoRemoteViews(mBuilder.mContext,
+                    bigContentView,
+                    innerView);
+            if (Build.VERSION.SDK_INT >= 21) {
+                setBackgroundColor(bigContentView);
+            }
+            return bigContentView;
+        }
+
+        @Override
+        int getBigContentViewLayoutResource(int actionCount) {
+            return actionCount <= 3
+                    ? R.layout.notification_template_big_media_narrow_custom
+                    : R.layout.notification_template_big_media_custom;
+        }
+
+        /**
+         * @hide
+         */
+        @RestrictTo(LIBRARY_GROUP)
+        @Override
+        public RemoteViews makeHeadsUpContentView(NotificationBuilderWithBuilderAccessor builder) {
+            if (Build.VERSION.SDK_INT >= 24) {
+                // No custom heads up content view required
+                return null;
+            }
+            RemoteViews innerView = mBuilder.getHeadsUpContentView() != null
+                    ? mBuilder.getHeadsUpContentView()
+                    : mBuilder.getContentView();
+            if (innerView == null) {
+                // No expandable notification
+                return null;
+            }
+            RemoteViews headsUpContentView = generateBigContentView();
+            NotificationCompatImplBase.buildIntoRemoteViews(mBuilder.mContext,
+                    headsUpContentView,
+                    innerView);
+            if (Build.VERSION.SDK_INT >= 21) {
+                setBackgroundColor(headsUpContentView);
+            }
+            return headsUpContentView;
+        }
+
+        private void setBackgroundColor(RemoteViews views) {
+            int color = mBuilder.getColor() != COLOR_DEFAULT
+                    ? mBuilder.getColor()
+                    : mBuilder.mContext.getResources().getColor(
+                        R.color.notification_material_background_media_default_color);
+            views.setInt(R.id.status_bar_latest_event_content, "setBackgroundColor", color);
+        }
     }
 }
diff --git a/v7/appcompat/src/android/support/v7/app/NotificationCompatImpl21.java b/v7/appcompat/src/android/support/v7/app/NotificationCompatImpl21.java
deleted file mode 100644
index 2a4bf7b..0000000
--- a/v7/appcompat/src/android/support/v7/app/NotificationCompatImpl21.java
+++ /dev/null
@@ -1,38 +0,0 @@
-/*
- * Copyright (C) 2015 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.support.v7.app;
-
-import android.app.Notification;
-import android.media.session.MediaSession;
-import android.support.annotation.RequiresApi;
-import android.support.v4.app.NotificationBuilderWithBuilderAccessor;
-
-@RequiresApi(21)
-class NotificationCompatImpl21 {
-
-    public static void addMediaStyle(NotificationBuilderWithBuilderAccessor b,
-            int[] actionsToShowInCompact,
-            Object token) {
-        Notification.MediaStyle style = new Notification.MediaStyle(b.getBuilder());
-        if (actionsToShowInCompact != null) {
-            style.setShowActionsInCompactView(actionsToShowInCompact);
-        }
-        if (token != null) {
-            style.setMediaSession((MediaSession.Token) token);
-        }
-    }
-}
diff --git a/v7/appcompat/src/android/support/v7/app/NotificationCompatImpl24.java b/v7/appcompat/src/android/support/v7/app/NotificationCompatImpl24.java
deleted file mode 100644
index cc09bb5..0000000
--- a/v7/appcompat/src/android/support/v7/app/NotificationCompatImpl24.java
+++ /dev/null
@@ -1,46 +0,0 @@
-/*
- * Copyright (C) 2016 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License
- */
-
-package android.support.v7.app;
-
-import android.app.Notification;
-import android.media.session.MediaSession;
-import android.support.annotation.RequiresApi;
-import android.support.v4.app.NotificationBuilderWithBuilderAccessor;
-
-@RequiresApi(24)
-class NotificationCompatImpl24 {
-
-    public static void addDecoratedCustomViewStyle(NotificationBuilderWithBuilderAccessor b) {
-        Notification.Builder builder = b.getBuilder();
-        builder.setStyle(new Notification.DecoratedCustomViewStyle());
-    }
-
-    public static void addDecoratedMediaCustomViewStyle(NotificationBuilderWithBuilderAccessor b,
-            int[] actionsToShowInCompact,
-            Object token) {
-        Notification.Builder builder = b.getBuilder();
-        Notification.DecoratedMediaCustomViewStyle style =
-                new Notification.DecoratedMediaCustomViewStyle();
-        if (actionsToShowInCompact != null) {
-            style.setShowActionsInCompactView(actionsToShowInCompact);
-        }
-        if (token != null) {
-            style.setMediaSession((MediaSession.Token) token);
-        }
-        builder.setStyle(style);
-    }
-}
diff --git a/v7/appcompat/src/android/support/v7/app/NotificationCompatImplBase.java b/v7/appcompat/src/android/support/v7/app/NotificationCompatImplBase.java
index c1432c9..b2d5eaa 100644
--- a/v7/appcompat/src/android/support/v7/app/NotificationCompatImplBase.java
+++ b/v7/appcompat/src/android/support/v7/app/NotificationCompatImplBase.java
@@ -16,8 +16,6 @@
 
 package android.support.v7.app;
 
-import android.app.Notification;
-import android.app.PendingIntent;
 import android.content.Context;
 import android.content.res.Resources;
 import android.graphics.Bitmap;
@@ -29,17 +27,13 @@
 import android.os.Build;
 import android.os.SystemClock;
 import android.support.annotation.RequiresApi;
-import android.support.v4.app.NotificationBuilderWithBuilderAccessor;
 import android.support.v4.app.NotificationCompat;
-import android.support.v4.app.NotificationCompatBase;
 import android.support.v7.appcompat.R;
 import android.util.TypedValue;
 import android.view.View;
 import android.widget.RemoteViews;
 
 import java.text.NumberFormat;
-import java.util.ArrayList;
-import java.util.List;
 
 /**
  * Helper class to generate MediaStyle notifications for pre-Lollipop platforms. Overrides
@@ -48,196 +42,7 @@
 @RequiresApi(9)
 class NotificationCompatImplBase {
 
-    static final int MAX_MEDIA_BUTTONS_IN_COMPACT = 3;
-    static final int MAX_MEDIA_BUTTONS = 5;
-    private static final int MAX_ACTION_BUTTONS = 3;
-
-    @RequiresApi(11)
-    public static <T extends NotificationCompatBase.Action> RemoteViews overrideContentViewMedia(
-            NotificationBuilderWithBuilderAccessor builder,
-            Context context, CharSequence contentTitle, CharSequence contentText,
-            CharSequence contentInfo, int number, Bitmap largeIcon, CharSequence subText,
-            boolean useChronometer, long when, int priority, List<T> actions,
-            int[] actionsToShowInCompact, boolean showCancelButton,
-            PendingIntent cancelButtonIntent, boolean isDecoratedCustomView) {
-        RemoteViews views = generateContentViewMedia(context, contentTitle, contentText, contentInfo,
-                number, largeIcon, subText, useChronometer, when, priority, actions,
-                actionsToShowInCompact, showCancelButton, cancelButtonIntent,
-                isDecoratedCustomView);
-        builder.getBuilder().setContent(views);
-        if (showCancelButton) {
-            builder.getBuilder().setOngoing(true);
-        }
-        return views;
-    }
-
-    @RequiresApi(11)
-    private static <T extends NotificationCompatBase.Action> RemoteViews generateContentViewMedia(
-            Context context, CharSequence contentTitle, CharSequence contentText,
-            CharSequence contentInfo, int number, Bitmap largeIcon, CharSequence subText,
-            boolean useChronometer, long when, int priority, List<T> actions,
-            int[] actionsToShowInCompact, boolean showCancelButton,
-            PendingIntent cancelButtonIntent, boolean isDecoratedCustomView) {
-        RemoteViews view = applyStandardTemplate(context, contentTitle, contentText, contentInfo,
-                number, 0 /* smallIcon */, largeIcon, subText, useChronometer, when, priority,
-                0 /* color is unused on media */,
-                isDecoratedCustomView ? R.layout.notification_template_media_custom
-                        : R.layout.notification_template_media,
-                true /* fitIn1U */);
-
-        final int numActions = actions.size();
-        final int N = actionsToShowInCompact == null
-                ? 0
-                : Math.min(actionsToShowInCompact.length, MAX_MEDIA_BUTTONS_IN_COMPACT);
-        view.removeAllViews(R.id.media_actions);
-        if (N > 0) {
-            for (int i = 0; i < N; i++) {
-                if (i >= numActions) {
-                    throw new IllegalArgumentException(String.format(
-                            "setShowActionsInCompactView: action %d out of bounds (max %d)",
-                            i, numActions - 1));
-                }
-
-                final NotificationCompatBase.Action action = actions.get(actionsToShowInCompact[i]);
-                final RemoteViews button = generateMediaActionButton(context, action);
-                view.addView(R.id.media_actions, button);
-            }
-        }
-        if (showCancelButton) {
-            view.setViewVisibility(R.id.end_padder, View.GONE);
-            view.setViewVisibility(R.id.cancel_action, View.VISIBLE);
-            view.setOnClickPendingIntent(R.id.cancel_action, cancelButtonIntent);
-            view.setInt(R.id.cancel_action, "setAlpha",
-                    context.getResources().getInteger(R.integer.cancel_button_image_alpha));
-        } else {
-            view.setViewVisibility(R.id.end_padder, View.VISIBLE);
-            view.setViewVisibility(R.id.cancel_action, View.GONE);
-        }
-        return view;
-    }
-
-    @RequiresApi(16)
-    public static <T extends NotificationCompatBase.Action> void overrideMediaBigContentView(
-            Notification n, Context context, CharSequence contentTitle, CharSequence contentText,
-            CharSequence contentInfo, int number, Bitmap largeIcon, CharSequence subText,
-            boolean useChronometer, long when, int priority, int color, List<T> actions,
-            boolean showCancelButton, PendingIntent cancelButtonIntent,
-            boolean decoratedCustomView) {
-        n.bigContentView = generateMediaBigView(context, contentTitle, contentText, contentInfo,
-                number, largeIcon, subText, useChronometer, when, priority, color, actions,
-                showCancelButton, cancelButtonIntent, decoratedCustomView);
-        if (showCancelButton) {
-            n.flags |= Notification.FLAG_ONGOING_EVENT;
-        }
-    }
-
-    @RequiresApi(11)
-    public static <T extends NotificationCompatBase.Action> RemoteViews generateMediaBigView(
-            Context context, CharSequence contentTitle, CharSequence contentText,
-            CharSequence contentInfo, int number, Bitmap largeIcon, CharSequence subText,
-            boolean useChronometer, long when, int priority, int color, List<T> actions,
-            boolean showCancelButton, PendingIntent cancelButtonIntent,
-            boolean decoratedCustomView) {
-        final int actionCount = Math.min(actions.size(), MAX_MEDIA_BUTTONS);
-        RemoteViews big = applyStandardTemplate(context, contentTitle, contentText, contentInfo,
-                number, 0 /* smallIcon */, largeIcon, subText, useChronometer, when, priority,
-                color,  /* fitIn1U */getBigMediaLayoutResource(decoratedCustomView, actionCount),
-                false);
-
-        big.removeAllViews(R.id.media_actions);
-        if (actionCount > 0) {
-            for (int i = 0; i < actionCount; i++) {
-                final RemoteViews button = generateMediaActionButton(context, actions.get(i));
-                big.addView(R.id.media_actions, button);
-            }
-        }
-        if (showCancelButton) {
-            big.setViewVisibility(R.id.cancel_action, View.VISIBLE);
-            big.setInt(R.id.cancel_action, "setAlpha",
-                    context.getResources().getInteger(R.integer.cancel_button_image_alpha));
-            big.setOnClickPendingIntent(R.id.cancel_action, cancelButtonIntent);
-        } else {
-            big.setViewVisibility(R.id.cancel_action, View.GONE);
-        }
-        return big;
-    }
-
-    @RequiresApi(11)
-    private static RemoteViews generateMediaActionButton(Context context,
-            NotificationCompatBase.Action action) {
-        final boolean tombstone = (action.getActionIntent() == null);
-        RemoteViews button = new RemoteViews(context.getPackageName(),
-                R.layout.notification_media_action);
-        button.setImageViewResource(R.id.action0, action.getIcon());
-        if (!tombstone) {
-            button.setOnClickPendingIntent(R.id.action0, action.getActionIntent());
-        }
-        if (Build.VERSION.SDK_INT >= 15) {
-            button.setContentDescription(R.id.action0, action.getTitle());
-        }
-        return button;
-    }
-
-    @RequiresApi(11)
-    private static int getBigMediaLayoutResource(boolean decoratedCustomView, int actionCount) {
-        if (actionCount <= 3) {
-            return decoratedCustomView
-                    ? R.layout.notification_template_big_media_narrow_custom
-                    : R.layout.notification_template_big_media_narrow;
-        } else {
-            return decoratedCustomView
-                    ? R.layout.notification_template_big_media_custom
-                    : R.layout.notification_template_big_media;
-        }
-    }
-
-    public static RemoteViews applyStandardTemplateWithActions(Context context,
-            CharSequence contentTitle, CharSequence contentText, CharSequence contentInfo,
-            int number, int smallIcon, Bitmap largeIcon, CharSequence subText,
-            boolean useChronometer, long when, int priority, int color, int resId, boolean fitIn1U,
-            ArrayList<NotificationCompat.Action> actions) {
-        RemoteViews remoteViews = applyStandardTemplate(context, contentTitle, contentText,
-                contentInfo, number, smallIcon, largeIcon, subText, useChronometer, when, priority,
-                color, resId, fitIn1U);
-        remoteViews.removeAllViews(R.id.actions);
-        boolean actionsVisible = false;
-        if (actions != null) {
-            int N = actions.size();
-            if (N > 0) {
-                actionsVisible = true;
-                if (N > MAX_ACTION_BUTTONS) N = MAX_ACTION_BUTTONS;
-                for (int i = 0; i < N; i++) {
-                    final RemoteViews button = generateActionButton(context, actions.get(i));
-                    remoteViews.addView(R.id.actions, button);
-                }
-            }
-        }
-        int actionVisibility = actionsVisible ? View.VISIBLE : View.GONE;
-        remoteViews.setViewVisibility(R.id.actions, actionVisibility);
-        remoteViews.setViewVisibility(R.id.action_divider, actionVisibility);
-        return remoteViews;
-    }
-
-    private static RemoteViews generateActionButton(Context context,
-            NotificationCompat.Action action) {
-        final boolean tombstone = (action.actionIntent == null);
-        RemoteViews button =  new RemoteViews(context.getPackageName(),
-                tombstone ? getActionTombstoneLayoutResource()
-                        : getActionLayoutResource());
-        button.setImageViewBitmap(R.id.action_image,
-                createColoredBitmap(context, action.getIcon(),
-                        context.getResources().getColor(R.color.notification_action_color_filter)));
-        button.setTextViewText(R.id.action_text, action.title);
-        if (!tombstone) {
-            button.setOnClickPendingIntent(R.id.action_container, action.actionIntent);
-        }
-        if (Build.VERSION.SDK_INT >= 15) {
-            button.setContentDescription(R.id.action_container, action.title);
-        }
-        return button;
-    }
-
-    private static Bitmap createColoredBitmap(Context context, int iconId, int color) {
+    static Bitmap createColoredBitmap(Context context, int iconId, int color) {
         return createColoredBitmap(context, iconId, color, 0);
     }
 
@@ -256,25 +61,14 @@
         return resultBitmap;
     }
 
-    private static int getActionLayoutResource() {
-        return R.layout.notification_action;
-    }
-
-    private static int getActionTombstoneLayoutResource() {
-        return R.layout.notification_action_tombstone;
-    }
-
-    public static RemoteViews applyStandardTemplate(Context context,
-            CharSequence contentTitle, CharSequence contentText, CharSequence contentInfo,
-            int number, int smallIcon, Bitmap largeIcon, CharSequence subText,
-            boolean useChronometer, long when, int priority, int color, int resId,
-            boolean fitIn1U) {
-        Resources res = context.getResources();
-        RemoteViews contentView = new RemoteViews(context.getPackageName(), resId);
+    public static RemoteViews applyStandardTemplate(NotificationCompat.Builder builder,
+            boolean showSmallIcon, int resId, boolean fitIn1U) {
+        Resources res = builder.mContext.getResources();
+        RemoteViews contentView = new RemoteViews(builder.mContext.getPackageName(), resId);
         boolean showLine3 = false;
         boolean showLine2 = false;
 
-        boolean minPriority = priority < NotificationCompat.PRIORITY_LOW;
+        boolean minPriority = builder.getPriority() < NotificationCompat.PRIORITY_LOW;
         if (Build.VERSION.SDK_INT >= 16 && Build.VERSION.SDK_INT < 21) {
             // lets color the backgrounds
             if (minPriority) {
@@ -290,34 +84,34 @@
             }
         }
 
-        if (largeIcon != null) {
+        if (builder.mLargeIcon != null) {
             // On versions before Jellybean, the large icon was shown by SystemUI, so we need to hide
             // it here.
             if (Build.VERSION.SDK_INT >= 16) {
                 contentView.setViewVisibility(R.id.icon, View.VISIBLE);
-                contentView.setImageViewBitmap(R.id.icon, largeIcon);
+                contentView.setImageViewBitmap(R.id.icon, builder.mLargeIcon);
             } else {
                 contentView.setViewVisibility(R.id.icon, View.GONE);
             }
-            if (smallIcon != 0) {
+            if (showSmallIcon && builder.mNotification.icon != 0) {
                 int backgroundSize = res.getDimensionPixelSize(
                         R.dimen.notification_right_icon_size);
                 int iconSize = backgroundSize - res.getDimensionPixelSize(
                         R.dimen.notification_small_icon_background_padding) * 2;
                 if (Build.VERSION.SDK_INT >= 21) {
-                    Bitmap smallBit = createIconWithBackground(context,
-                            smallIcon,
+                    Bitmap smallBit = createIconWithBackground(builder.mContext,
+                            builder.mNotification.icon,
                             backgroundSize,
                             iconSize,
-                            color);
+                            builder.getColor());
                     contentView.setImageViewBitmap(R.id.right_icon, smallBit);
                 } else {
-                    contentView.setImageViewBitmap(R.id.right_icon,
-                            createColoredBitmap(context, smallIcon, Color.WHITE));
+                    contentView.setImageViewBitmap(R.id.right_icon, createColoredBitmap(
+                            builder.mContext, builder.mNotification.icon, Color.WHITE));
                 }
                 contentView.setViewVisibility(R.id.right_icon, View.VISIBLE);
             }
-        } else if (smallIcon != 0) { // small icon at left
+        } else if (showSmallIcon && builder.mNotification.icon != 0) { // small icon at left
             contentView.setViewVisibility(R.id.icon, View.VISIBLE);
             if (Build.VERSION.SDK_INT >= 21) {
                 int backgroundSize = res.getDimensionPixelSize(
@@ -325,40 +119,40 @@
                         - res.getDimensionPixelSize(R.dimen.notification_big_circle_margin);
                 int iconSize = res.getDimensionPixelSize(
                         R.dimen.notification_small_icon_size_as_large);
-                Bitmap smallBit = createIconWithBackground(context,
-                        smallIcon,
+                Bitmap smallBit = createIconWithBackground(builder.mContext,
+                        builder.mNotification.icon,
                         backgroundSize,
                         iconSize,
-                        color);
+                        builder.getColor());
                 contentView.setImageViewBitmap(R.id.icon, smallBit);
             } else {
-                contentView.setImageViewBitmap(R.id.icon,
-                        createColoredBitmap(context, smallIcon, Color.WHITE));
+                contentView.setImageViewBitmap(R.id.icon, createColoredBitmap(
+                        builder.mContext, builder.mNotification.icon, Color.WHITE));
             }
         }
-        if (contentTitle != null) {
-            contentView.setTextViewText(R.id.title, contentTitle);
+        if (builder.mContentTitle != null) {
+            contentView.setTextViewText(R.id.title, builder.mContentTitle);
         }
-        if (contentText != null) {
-            contentView.setTextViewText(R.id.text, contentText);
+        if (builder.mContentText != null) {
+            contentView.setTextViewText(R.id.text, builder.mContentText);
             showLine3 = true;
         }
         // If there is a large icon we have a right side
-        boolean hasRightSide = !(Build.VERSION.SDK_INT >= 21) && largeIcon != null;
-        if (contentInfo != null) {
-            contentView.setTextViewText(R.id.info, contentInfo);
+        boolean hasRightSide = !(Build.VERSION.SDK_INT >= 21) && builder.mLargeIcon != null;
+        if (builder.mContentInfo != null) {
+            contentView.setTextViewText(R.id.info, builder.mContentInfo);
             contentView.setViewVisibility(R.id.info, View.VISIBLE);
             showLine3 = true;
             hasRightSide = true;
-        } else if (number > 0) {
+        } else if (builder.mNumber > 0) {
             final int tooBig = res.getInteger(
                     R.integer.status_bar_notification_info_maxnum);
-            if (number > tooBig) {
+            if (builder.mNumber > tooBig) {
                 contentView.setTextViewText(R.id.info, ((Resources) res).getString(
                         R.string.status_bar_notification_info_overflow));
             } else {
                 NumberFormat f = NumberFormat.getIntegerInstance();
-                contentView.setTextViewText(R.id.info, f.format(number));
+                contentView.setTextViewText(R.id.info, f.format(builder.mNumber));
             }
             contentView.setViewVisibility(R.id.info, View.VISIBLE);
             showLine3 = true;
@@ -368,10 +162,10 @@
         }
 
         // Need to show three lines? Only allow on Jellybean+
-        if (subText != null && Build.VERSION.SDK_INT >= 16) {
-            contentView.setTextViewText(R.id.text, subText);
-            if (contentText != null) {
-                contentView.setTextViewText(R.id.text2, contentText);
+        if (builder.mSubText != null && Build.VERSION.SDK_INT >= 16) {
+            contentView.setTextViewText(R.id.text, builder.mSubText);
+            if (builder.mContentText != null) {
+                contentView.setTextViewText(R.id.text2, builder.mContentText);
                 contentView.setViewVisibility(R.id.text2, View.VISIBLE);
                 showLine2 = true;
             } else {
@@ -391,15 +185,16 @@
             contentView.setViewPadding(R.id.line1, 0, 0, 0, 0);
         }
 
-        if (when != 0) {
-            if (useChronometer && Build.VERSION.SDK_INT >= 16) {
+        if (builder.getWhenIfShowing() != 0) {
+            if (builder.mUseChronometer && Build.VERSION.SDK_INT >= 16) {
                 contentView.setViewVisibility(R.id.chronometer, View.VISIBLE);
                 contentView.setLong(R.id.chronometer, "setBase",
-                        when + (SystemClock.elapsedRealtime() - System.currentTimeMillis()));
+                        builder.getWhenIfShowing()
+                                + (SystemClock.elapsedRealtime() - System.currentTimeMillis()));
                 contentView.setBoolean(R.id.chronometer, "setStarted", true);
             } else {
                 contentView.setViewVisibility(R.id.time, View.VISIBLE);
-                contentView.setLong(R.id.time, "setTime", when);
+                contentView.setLong(R.id.time, "setTime", builder.getWhenIfShowing());
             }
             hasRightSide = true;
         }
diff --git a/v7/appcompat/src/android/support/v7/app/NotificationCompatImplJellybean.java b/v7/appcompat/src/android/support/v7/app/NotificationCompatImplJellybean.java
deleted file mode 100644
index 2fca0f0..0000000
--- a/v7/appcompat/src/android/support/v7/app/NotificationCompatImplJellybean.java
+++ /dev/null
@@ -1,31 +0,0 @@
-/*
- * Copyright (C) 2016 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License
- */
-
-package android.support.v7.app;
-
-import android.app.Notification;
-import android.support.annotation.RequiresApi;
-import android.support.v4.app.NotificationBuilderWithBuilderAccessor;
-
-@RequiresApi(16)
-class NotificationCompatImplJellybean {
-
-    public static void addBigTextStyle(NotificationBuilderWithBuilderAccessor b,
-            CharSequence bigText) {
-        Notification.BigTextStyle bigTextStyle = new Notification.BigTextStyle(b.getBuilder());
-        bigTextStyle.bigText(bigText);
-    }
-}
diff --git a/v7/appcompat/src/android/support/v7/widget/AppCompatButton.java b/v7/appcompat/src/android/support/v7/widget/AppCompatButton.java
index 04698e2..2156dce 100644
--- a/v7/appcompat/src/android/support/v7/widget/AppCompatButton.java
+++ b/v7/appcompat/src/android/support/v7/widget/AppCompatButton.java
@@ -22,16 +22,21 @@
 import android.content.res.ColorStateList;
 import android.graphics.PorterDuff;
 import android.graphics.drawable.Drawable;
+import android.os.Build;
 import android.support.annotation.DrawableRes;
+import android.support.annotation.NonNull;
 import android.support.annotation.Nullable;
 import android.support.annotation.RequiresApi;
 import android.support.annotation.RestrictTo;
 import android.support.v4.view.TintableBackgroundView;
+import android.support.v4.widget.AutoSizeableTextView;
+import android.support.v4.widget.TextViewCompat;
 import android.support.v7.appcompat.R;
 import android.util.AttributeSet;
 import android.view.accessibility.AccessibilityEvent;
 import android.view.accessibility.AccessibilityNodeInfo;
 import android.widget.Button;
+import android.widget.TextView;
 
 /**
  * A {@link Button} which supports compatible features on older versions of the platform,
@@ -48,7 +53,8 @@
  * <a href="{@docRoot}topic/libraries/support-library/packages.html#v7-appcompat">appcompat</a>.
  * You should only need to manually use this class when writing custom views.</p>
  */
-public class AppCompatButton extends Button implements TintableBackgroundView {
+public class AppCompatButton extends Button implements TintableBackgroundView,
+        AutoSizeableTextView {
 
     private final AppCompatBackgroundHelper mBackgroundTintHelper;
     private final AppCompatTextHelper mTextHelper;
@@ -176,6 +182,169 @@
         info.setClassName(Button.class.getName());
     }
 
+    @Override
+    protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
+        super.onLayout(changed, left, top, right, bottom);
+        if (mTextHelper != null) {
+            mTextHelper.onLayout(changed, left, top, right, bottom);
+        }
+    }
+
+    @Override
+    public void setTextSize(int unit, float size) {
+        if (Build.VERSION.SDK_INT >= 26) {
+            super.setTextSize(unit, size);
+        } else {
+            if (mTextHelper != null) {
+                mTextHelper.setTextSize(unit, size);
+            }
+        }
+    }
+
+    @Override
+    protected void onTextChanged(CharSequence text, int start, int lengthBefore, int lengthAfter) {
+        super.onTextChanged(text, start, lengthBefore, lengthAfter);
+        if (mTextHelper != null && Build.VERSION.SDK_INT < 26 && mTextHelper.isAutoSizeEnabled()) {
+            mTextHelper.autoSizeText();
+        }
+    }
+
+    /**
+     * @hide
+     */
+    @RestrictTo(LIBRARY_GROUP)
+    @Override
+    public void setAutoSizeTextTypeWithDefaults(
+            @TextViewCompat.AutoSizeTextType int autoSizeTextType) {
+        if (Build.VERSION.SDK_INT >= 26) {
+            super.setAutoSizeTextTypeWithDefaults(autoSizeTextType);
+        } else {
+            if (mTextHelper != null) {
+                mTextHelper.setAutoSizeTextTypeWithDefaults(autoSizeTextType);
+            }
+        }
+    }
+
+    /**
+     * @hide
+     */
+    @RestrictTo(LIBRARY_GROUP)
+    @Override
+    public void setAutoSizeTextTypeUniformWithConfiguration(
+            int autoSizeMinTextSize,
+            int autoSizeMaxTextSize,
+            int autoSizeStepGranularity,
+            int unit) throws IllegalArgumentException {
+        if (Build.VERSION.SDK_INT >= 26) {
+            super.setAutoSizeTextTypeUniformWithConfiguration(
+                    autoSizeMinTextSize, autoSizeMaxTextSize, autoSizeStepGranularity, unit);
+        } else {
+            if (mTextHelper != null) {
+                mTextHelper.setAutoSizeTextTypeUniformWithConfiguration(
+                        autoSizeMinTextSize, autoSizeMaxTextSize, autoSizeStepGranularity, unit);
+            }
+        }
+    }
+
+    /**
+     * @hide
+     */
+    @RestrictTo(LIBRARY_GROUP)
+    @Override
+    public void setAutoSizeTextTypeUniformWithPresetSizes(@NonNull int[] presetSizes, int unit)
+            throws IllegalArgumentException {
+        if (Build.VERSION.SDK_INT >= 26) {
+            super.setAutoSizeTextTypeUniformWithPresetSizes(presetSizes, unit);
+        } else {
+            if (mTextHelper != null) {
+                mTextHelper.setAutoSizeTextTypeUniformWithPresetSizes(presetSizes, unit);
+            }
+        }
+    }
+
+    /**
+     * @hide
+     */
+    @RestrictTo(LIBRARY_GROUP)
+    @Override
+    @TextViewCompat.AutoSizeTextType
+    public int getAutoSizeTextType() {
+        if (Build.VERSION.SDK_INT >= 26) {
+            return super.getAutoSizeTextType() == TextView.AUTO_SIZE_TEXT_TYPE_UNIFORM
+                    ? TextViewCompat.AUTO_SIZE_TEXT_TYPE_UNIFORM
+                    : TextViewCompat.AUTO_SIZE_TEXT_TYPE_NONE;
+        } else {
+            if (mTextHelper != null) {
+                return mTextHelper.getAutoSizeTextType();
+            }
+        }
+        return TextViewCompat.AUTO_SIZE_TEXT_TYPE_NONE;
+    }
+
+    /**
+     * @hide
+     */
+    @RestrictTo(LIBRARY_GROUP)
+    @Override
+    public int getAutoSizeStepGranularity() {
+        if (Build.VERSION.SDK_INT >= 26) {
+            return super.getAutoSizeStepGranularity();
+        } else {
+            if (mTextHelper != null) {
+                return mTextHelper.getAutoSizeStepGranularity();
+            }
+        }
+        return -1;
+    }
+
+    /**
+     * @hide
+     */
+    @RestrictTo(LIBRARY_GROUP)
+    @Override
+    public int getAutoSizeMinTextSize() {
+        if (Build.VERSION.SDK_INT >= 26) {
+            return super.getAutoSizeMinTextSize();
+        } else {
+            if (mTextHelper != null) {
+                return mTextHelper.getAutoSizeMinTextSize();
+            }
+        }
+        return -1;
+    }
+
+    /**
+     * @hide
+     */
+    @RestrictTo(LIBRARY_GROUP)
+    @Override
+    public int getAutoSizeMaxTextSize() {
+        if (Build.VERSION.SDK_INT >= 26) {
+            return super.getAutoSizeMaxTextSize();
+        } else {
+            if (mTextHelper != null) {
+                return mTextHelper.getAutoSizeMaxTextSize();
+            }
+        }
+        return -1;
+    }
+
+    /**
+     * @hide
+     */
+    @RestrictTo(LIBRARY_GROUP)
+    @Override
+    public int[] getAutoSizeTextAvailableSizes() {
+        if (Build.VERSION.SDK_INT >= 26) {
+            return super.getAutoSizeTextAvailableSizes();
+        } else {
+            if (mTextHelper != null) {
+                return mTextHelper.getAutoSizeTextAvailableSizes();
+            }
+        }
+        return new int[0];
+    }
+
     /**
      * Sets the properties of this field to transform input to ALL CAPS
      * display. This may use a "small caps" formatting if available.
diff --git a/v7/appcompat/src/android/support/v7/widget/AppCompatTextViewAutoSizeHelper.java b/v7/appcompat/src/android/support/v7/widget/AppCompatTextViewAutoSizeHelper.java
index 8e1f534..29449f5 100644
--- a/v7/appcompat/src/android/support/v7/widget/AppCompatTextViewAutoSizeHelper.java
+++ b/v7/appcompat/src/android/support/v7/widget/AppCompatTextViewAutoSizeHelper.java
@@ -25,6 +25,7 @@
 import android.graphics.RectF;
 import android.os.Build;
 import android.support.annotation.NonNull;
+import android.support.annotation.Nullable;
 import android.support.annotation.RestrictTo;
 import android.support.v4.widget.TextViewCompat;
 import android.support.v7.appcompat.R;
@@ -35,6 +36,7 @@
 import android.text.TextPaint;
 import android.util.AttributeSet;
 import android.util.DisplayMetrics;
+import android.util.Log;
 import android.util.TypedValue;
 import android.widget.TextView;
 
@@ -53,6 +55,7 @@
  * fill its layout based on the TextView's characteristics and boundaries.
  */
 class AppCompatTextViewAutoSizeHelper {
+    private static final String TAG = "ACTVAutoSizeHelper";
     private static final RectF TEMP_RECTF = new RectF();
     // Default minimum size for auto-sizing text in scaled pixels.
     private static final int DEFAULT_AUTO_SIZE_MIN_TEXT_SIZE_IN_SP = 12;
@@ -60,6 +63,9 @@
     private static final int DEFAULT_AUTO_SIZE_MAX_TEXT_SIZE_IN_SP = 112;
     // Default value for the step size in pixels.
     private static final int DEFAULT_AUTO_SIZE_GRANULARITY_IN_PX = 1;
+    // Cache of TextView methods used via reflection; the key is the method name and the value is
+    // the method itself or null if it can not be found.
+    private static Hashtable<String, Method> sTextViewMethodByNameCache = new Hashtable<>();
     // Use this to specify that any of the auto-size configuration int values have not been set.
     static final float UNSET_AUTO_SIZE_UNIFORM_CONFIGURATION_VALUE = -1f;
     // Ported from TextView#VERY_WIDE. Represents a maximum width in pixels the TextView takes when
@@ -82,9 +88,7 @@
     // build the steps set using mAutoSizeMinTextSizeInPx, mAutoSizeMaxTextSizeInPx and
     // mAutoSizeStepGranularityInPx.
     private boolean mHasPresetAutoSizeValues = false;
-
     private TextPaint mTempTextPaint;
-    private Hashtable<String, Method> mMethodByNameCache = new Hashtable<>();
 
     private final TextView mTextView;
     private final Context mContext;
@@ -606,23 +610,14 @@
                 // Do not auto-size right after setting the text size.
                 mNeedsAutoSizeText = false;
 
+                final String methodName = "nullLayouts";
                 try {
-                    final String methodName = "nullLayouts";
-                    Method method = mMethodByNameCache.get(methodName);
-                    if (method == null) {
-                        method = TextView.class.getDeclaredMethod(methodName);
-                        if (method != null) {
-                            method.setAccessible(true);
-                            // Cache update.
-                            mMethodByNameCache.put(methodName, method);
-                        }
-                    }
-
+                    Method method = getTextViewMethod(methodName);
                     if (method != null) {
                         method.invoke(mTextView);
                     }
                 } catch (Exception ex) {
-                    // Nothing to do.
+                    Log.w(TAG, "Failed to invoke TextView#" + methodName + "() method", ex);
                 }
 
                 if (!isInLayout) {
@@ -754,25 +749,18 @@
                 includePad);
     }
 
-    private <T> T invokeAndReturnWithDefault(@NonNull Object object, @NonNull String methodName,
-            @NonNull T defaultValue) {
+    private <T> T invokeAndReturnWithDefault(@NonNull Object object,
+            @NonNull final String methodName, @NonNull final T defaultValue) {
         T result = null;
         boolean exceptionThrown = false;
 
         try {
             // Cache lookup.
-            Method method = mMethodByNameCache.get(methodName);
-            if (method == null) {
-                method = TextView.class.getDeclaredMethod(methodName);
-                if (method != null) {
-                    method.setAccessible(true);
-                    // Cache update.
-                    mMethodByNameCache.put(methodName, method);
-                }
-            }
+            Method method = getTextViewMethod(methodName);
             result = (T) method.invoke(object);
-        } catch (Exception e) {
+        } catch (Exception ex) {
             exceptionThrown = true;
+            Log.w(TAG, "Failed to invoke TextView#" + methodName + "() method", ex);
         } finally {
             if (result == null && exceptionThrown) {
                 result = defaultValue;
@@ -782,6 +770,26 @@
         return result;
     }
 
+    @Nullable
+    private Method getTextViewMethod(@NonNull final String methodName) {
+        try {
+            Method method = sTextViewMethodByNameCache.get(methodName);
+            if (method == null) {
+                method = TextView.class.getDeclaredMethod(methodName);
+                if (method != null) {
+                    method.setAccessible(true);
+                    // Cache update.
+                    sTextViewMethodByNameCache.put(methodName, method);
+                }
+            }
+
+            return method;
+        } catch (Exception ex) {
+            Log.w(TAG, "Failed to retrieve TextView#" + methodName + "() method", ex);
+            return null;
+        }
+    }
+
     /**
      * @return {@code true} if this widget supports auto-sizing text and has been configured to
      * auto-size.
diff --git a/v7/appcompat/tests/AndroidManifest.xml b/v7/appcompat/tests/AndroidManifest.xml
index cb58573..abad7d6 100644
--- a/v7/appcompat/tests/AndroidManifest.xml
+++ b/v7/appcompat/tests/AndroidManifest.xml
@@ -79,6 +79,11 @@
             android:theme="@style/Theme.AppCompat.Light"/>
 
         <activity
+            android:name="android.support.v7.widget.AppCompatButtonAutoSizeActivity"
+            android:label="@string/app_compat_button_auto_size_activity"
+            android:theme="@style/Theme.AppCompat.Light"/>
+
+        <activity
             android:name="android.support.v7.widget.AppCompatImageButtonActivity"
             android:label="@string/app_compat_image_button_activity"
             android:theme="@style/Theme.AppCompat.Light"/>
diff --git a/v7/appcompat/tests/res/layout/appcompat_button_autosize_activity.xml b/v7/appcompat/tests/res/layout/appcompat_button_autosize_activity.xml
new file mode 100644
index 0000000..407154a
--- /dev/null
+++ b/v7/appcompat/tests/res/layout/appcompat_button_autosize_activity.xml
@@ -0,0 +1,57 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  ~ Copyright (C) 2017 The Android Open Source Project
+  ~
+  ~ Licensed under the Apache License, Version 2.0 (the "License");
+  ~ you may not use this file except in compliance with the License.
+  ~ You may obtain a copy of the License at
+  ~
+  ~      http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing, software
+  ~ distributed under the License is distributed on an "AS IS" BASIS,
+  ~ WITHOUT 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:app="http://schemas.android.com/apk/res-auto"
+    android:id="@+id/container"
+    android:layout_width="match_parent"
+    android:layout_height="wrap_content"
+    android:orientation="vertical">
+
+    <android.support.v7.widget.AppCompatButton
+        android:id="@+id/view_autosize_uniform"
+        android:layout_width="100dp"
+        android:layout_height="200dp"
+        android:text="@string/sample_text1"
+        app:autoSizeTextType="uniform"
+        app:autoSizeMinTextSize="2px"
+        app:autoSizeMaxTextSize="50dp"
+        app:autoSizeStepGranularity="1px" />
+
+    <android.support.v7.widget.AppCompatButton
+        android:id="@+id/view_autosize_uniform_predef_sizes"
+        android:layout_width="100dp"
+        android:layout_height="200dp"
+        android:text="@string/sample_text1"
+        app:autoSizeTextType="uniform"
+        app:autoSizePresetSizes="@array/auto_size_predefined_sizes" />
+
+    <android.support.v7.widget.AppCompatButton
+        android:id="@+id/view_autosize_uniform_predef_sizes_redundant_values"
+        android:layout_width="100dp"
+        android:layout_height="200dp"
+        android:text="@string/sample_text1"
+        app:autoSizeTextType="uniform"
+        app:autoSizePresetSizes="@array/auto_size_predefined_sizes_redundant_values" />
+
+    <android.support.v7.widget.AppCompatButton
+        android:id="@+id/view_text"
+        android:layout_width="100dp"
+        android:layout_height="200dp"
+        android:text="@string/sample_text1" />
+
+</LinearLayout>
\ No newline at end of file
diff --git a/v7/appcompat/tests/res/layout/appcompat_textview_autosize_activity.xml b/v7/appcompat/tests/res/layout/appcompat_textview_autosize_activity.xml
index b9df4cc..f9aafcc 100644
--- a/v7/appcompat/tests/res/layout/appcompat_textview_autosize_activity.xml
+++ b/v7/appcompat/tests/res/layout/appcompat_textview_autosize_activity.xml
@@ -16,13 +16,13 @@
 <LinearLayout
     xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:app="http://schemas.android.com/apk/res-auto"
-    android:id="@+id/layout_textviewtest"
+    android:id="@+id/container"
     android:layout_width="match_parent"
     android:layout_height="wrap_content"
-    android:orientation="horizontal">
+    android:orientation="vertical">
 
     <android.support.v7.widget.AppCompatTextView
-        android:id="@+id/textview_autosize_uniform"
+        android:id="@+id/view_autosize_uniform"
         android:layout_width="100dp"
         android:layout_height="200dp"
         android:text="@string/sample_text1"
@@ -32,7 +32,7 @@
         app:autoSizeStepGranularity="1px" />
 
     <android.support.v7.widget.AppCompatTextView
-        android:id="@+id/textview_autosize_uniform_predef_sizes"
+        android:id="@+id/view_autosize_uniform_predef_sizes"
         android:layout_width="100dp"
         android:layout_height="200dp"
         android:text="@string/sample_text1"
@@ -40,7 +40,7 @@
         app:autoSizePresetSizes="@array/auto_size_predefined_sizes" />
 
     <android.support.v7.widget.AppCompatTextView
-        android:id="@+id/textview_autosize_uniform_predef_sizes_redundant_values"
+        android:id="@+id/view_autosize_uniform_predef_sizes_redundant_values"
         android:layout_width="100dp"
         android:layout_height="200dp"
         android:text="@string/sample_text1"
@@ -48,10 +48,10 @@
         app:autoSizePresetSizes="@array/auto_size_predefined_sizes_redundant_values" />
 
     <android.support.v7.widget.AppCompatTextView
-        android:id="@+id/textview_text"
-        android:text="@string/sample_text1"
+        android:id="@+id/view_text"
         android:layout_width="100dp"
-        android:layout_height="200dp"/>
+        android:layout_height="200dp"
+        android:text="@string/sample_text1" />
 
     <android.support.v7.widget.AppCompatEditText
         android:id="@+id/edittext_autosize_uniform"
diff --git a/v7/appcompat/tests/res/values/strings.xml b/v7/appcompat/tests/res/values/strings.xml
index 86386e2..90be8b3 100644
--- a/v7/appcompat/tests/res/values/strings.xml
+++ b/v7/appcompat/tests/res/values/strings.xml
@@ -56,6 +56,7 @@
     <string name="app_compat_spinner_activity">AppCompat spinner</string>
     <string name="app_compat_text_view_activity">AppCompat text view</string>
     <string name="app_compat_text_view_auto_size_activity">AppCompat text view auto-size</string>
+    <string name="app_compat_button_auto_size_activity">AppCompat button auto-size</string>
     <string name="sample_text1">Sample text 1</string>
     <string name="sample_text2">Sample text 2</string>
     <string name="app_compat_image_button_activity">AppCompat image button</string>
diff --git a/v7/appcompat/tests/src/android/support/v7/widget/AppCompatBaseAutoSizeTest.java b/v7/appcompat/tests/src/android/support/v7/widget/AppCompatBaseAutoSizeTest.java
new file mode 100644
index 0000000..cc6f91b
--- /dev/null
+++ b/v7/appcompat/tests/src/android/support/v7/widget/AppCompatBaseAutoSizeTest.java
@@ -0,0 +1,1210 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.support.v7.widget;
+
+import static org.junit.Assert.assertArrayEquals;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotEquals;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertTrue;
+
+import android.app.Instrumentation;
+import android.graphics.Typeface;
+import android.graphics.drawable.Drawable;
+import android.os.Build;
+import android.support.annotation.IdRes;
+import android.support.test.InstrumentationRegistry;
+import android.support.test.filters.MediumTest;
+import android.support.test.filters.SdkSuppress;
+import android.support.test.filters.SmallTest;
+import android.support.test.rule.ActivityTestRule;
+import android.support.v4.content.res.ResourcesCompat;
+import android.support.v4.widget.AutoSizeableTextView;
+import android.support.v4.widget.TextViewCompat;
+import android.support.v7.appcompat.test.R;
+import android.support.v7.testutils.BaseTestActivity;
+import android.text.TextUtils;
+import android.util.DisplayMetrics;
+import android.util.TypedValue;
+import android.view.ViewGroup;
+import android.widget.LinearLayout;
+import android.widget.TextView;
+
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+
+/**
+ * Base class for testing auto-size-text enabled views in appcompat-v7 that implement the
+ * <code>AutoSizeableTextView</code> interface. Extensions of this class run all tests
+ * from here and can add test cases specific to the functionality they add to the relevant
+ * base view class.
+ */
+public abstract class AppCompatBaseAutoSizeTest<A extends BaseTestActivity,
+        T extends TextView & AutoSizeableTextView> {
+
+    @Rule
+    public final ActivityTestRule<A> mActivityTestRule;
+
+    protected A mActivity;
+    protected Instrumentation mInstrumentation;
+    protected ViewGroup mContainer;
+
+    public AppCompatBaseAutoSizeTest(Class clazz) {
+        mActivityTestRule = new ActivityTestRule<A>(clazz);
+    }
+
+    @Before
+    public void setup() {
+        mInstrumentation = InstrumentationRegistry.getInstrumentation();
+        mActivity = mActivityTestRule.getActivity();
+        mContainer = mActivity.findViewById(R.id.container);
+    }
+
+    @Test
+    @MediumTest
+    @SdkSuppress(minSdkVersion = 16)
+    // public TextView#getMaxLines only introduced in API 16.
+    public void testAutoSizeCallers_setMaxLines() throws Throwable {
+        final T autoSizeView = prepareAndRetrieveAutoSizeTestData(R.id.view_autosize_uniform,
+                false);
+        // Configure layout params and auto-size both in pixels to dodge flakiness on different
+        // devices.
+        final LinearLayout.LayoutParams layoutParams = new LinearLayout.LayoutParams(
+                500, 500);
+        final String text = "one two three four five six seven eight nine ten";
+        mActivity.runOnUiThread(new Runnable() {
+            @Override
+            public void run() {
+                autoSizeView.setLayoutParams(layoutParams);
+                ((AutoSizeableTextView) autoSizeView).setAutoSizeTextTypeUniformWithConfiguration(
+                        1 /* autoSizeMinTextSize */,
+                        5000 /* autoSizeMaxTextSize */,
+                        1 /* autoSizeStepGranularity */,
+                        TypedValue.COMPLEX_UNIT_PX);
+                autoSizeView.setText(text);
+            }
+        });
+        mInstrumentation.waitForIdleSync();
+
+        float initialSize = 0;
+        for (int i = 1; i < 10; i++) {
+            final int maxLines = i;
+            mActivity.runOnUiThread(new Runnable() {
+                @Override
+                public void run() {
+                    autoSizeView.setMaxLines(maxLines);
+                }
+            });
+            mInstrumentation.waitForIdleSync();
+            float expectedSmallerSize = autoSizeView.getTextSize();
+            if (i == 1) {
+                initialSize = expectedSmallerSize;
+            }
+
+            mActivity.runOnUiThread(new Runnable() {
+                @Override
+                public void run() {
+                    autoSizeView.setMaxLines(maxLines + 1);
+                }
+            });
+            mInstrumentation.waitForIdleSync();
+            assertTrue(expectedSmallerSize <= autoSizeView.getTextSize());
+        }
+        assertTrue(initialSize < autoSizeView.getTextSize());
+
+        initialSize = 999999;
+        for (int i = 10; i > 1; i--) {
+            final int maxLines = i;
+            mActivity.runOnUiThread(new Runnable() {
+                @Override
+                public void run() {
+                    autoSizeView.setMaxLines(maxLines);
+                }
+            });
+            mInstrumentation.waitForIdleSync();
+            float expectedLargerSize = autoSizeView.getTextSize();
+            if (i == 10) {
+                initialSize = expectedLargerSize;
+            }
+
+            mActivity.runOnUiThread(new Runnable() {
+                @Override
+                public void run() {
+                    autoSizeView.setMaxLines(maxLines - 1);
+                }
+            });
+            mInstrumentation.waitForIdleSync();
+            assertTrue(expectedLargerSize >= autoSizeView.getTextSize());
+        }
+        assertTrue(initialSize > autoSizeView.getTextSize());
+    }
+
+    @Test
+    @MediumTest
+    public void testAutoSizeUniform_autoSizeCalledWhenTypeChanged() throws Throwable {
+        final T view = mContainer.findViewById(R.id.view_text);
+
+        // Make sure we pick an already inflated non auto-sized text view.
+        assertEquals(TextViewCompat.AUTO_SIZE_TEXT_TYPE_NONE,
+                ((AutoSizeableTextView) view).getAutoSizeTextType());
+        // Set the text size to a very low value in order to prepare for auto-size.
+        final int customTextSize = 3;
+        mActivityTestRule.runOnUiThread(new Runnable() {
+            @Override
+            public void run() {
+                view.setTextSize(TypedValue.COMPLEX_UNIT_PX, customTextSize);
+            }
+        });
+        mInstrumentation.waitForIdleSync();
+        assertEquals(customTextSize, view.getTextSize(), 0f);
+        mActivityTestRule.runOnUiThread(new Runnable() {
+            @Override
+            public void run() {
+                ((AutoSizeableTextView) view).setAutoSizeTextTypeWithDefaults(
+                        TextViewCompat.AUTO_SIZE_TEXT_TYPE_UNIFORM);
+            }
+        });
+        mInstrumentation.waitForIdleSync();
+        // The size of the text should have changed.
+        assertNotEquals(customTextSize, view.getTextSize(), 0f);
+    }
+
+    @Test
+    @MediumTest
+    public void testAutoSizeCallers_setText() throws Throwable {
+        final T autoSizeView = prepareAndRetrieveAutoSizeTestData(R.id.view_autosize_uniform,
+                false);
+
+        // Configure layout params and auto-size both in pixels to dodge flakiness on different
+        // devices.
+        final LinearLayout.LayoutParams layoutParams = new LinearLayout.LayoutParams(
+                500, 500);
+        mActivity.runOnUiThread(new Runnable() {
+            @Override
+            public void run() {
+                autoSizeView.setLayoutParams(layoutParams);
+                ((AutoSizeableTextView) autoSizeView).setAutoSizeTextTypeUniformWithConfiguration(
+                        1, 5000, 1, TypedValue.COMPLEX_UNIT_PX);
+            }
+        });
+        mInstrumentation.waitForIdleSync();
+
+        final String initialText = "13characters ";
+        final StringBuilder textToSet = new StringBuilder().append(initialText);
+        float initialSize = 0;
+
+        // As we add characters the text size shrinks.
+        for (int i = 0; i < 10; i++) {
+            mActivity.runOnUiThread(new Runnable() {
+                @Override
+                public void run() {
+                    autoSizeView.setText(textToSet.toString());
+                }
+            });
+
+            mInstrumentation.waitForIdleSync();
+            float expectedLargerSize = autoSizeView.getTextSize();
+            if (i == 0) {
+                initialSize = expectedLargerSize;
+            }
+
+            textToSet.append(initialText);
+            mActivity.runOnUiThread(new Runnable() {
+                @Override
+                public void run() {
+                    autoSizeView.setText(textToSet.toString());
+                }
+            });
+            mInstrumentation.waitForIdleSync();
+            assertTrue(expectedLargerSize >= autoSizeView.getTextSize());
+        }
+        assertTrue(initialSize > autoSizeView.getTextSize());
+
+        initialSize = 9999999;
+        // As we remove characters the text size expands.
+        for (int i = 9; i >= 0; i--) {
+            mActivity.runOnUiThread(new Runnable() {
+                @Override
+                public void run() {
+                    autoSizeView.setText(textToSet.toString());
+                }
+            });
+            mInstrumentation.waitForIdleSync();
+            float expectedSmallerSize = autoSizeView.getTextSize();
+            if (i == 0) {
+                initialSize = expectedSmallerSize;
+            }
+
+            textToSet.replace((textToSet.length() - initialText.length()),
+                    textToSet.length(), "");
+            mActivity.runOnUiThread(new Runnable() {
+                @Override
+                public void run() {
+                    autoSizeView.setText(textToSet.toString());
+                }
+            });
+            mInstrumentation.waitForIdleSync();
+
+            assertTrue(autoSizeView.getTextSize() >= expectedSmallerSize);
+        }
+        assertTrue(autoSizeView.getTextSize() > initialSize);
+    }
+
+    @Test
+    @MediumTest
+    public void testAutoSizeUniform_equivalentConfigurations() throws Throwable {
+        final DisplayMetrics dm = mActivity.getResources().getDisplayMetrics();
+        final int minTextSize = 10;
+        final int maxTextSize = 20;
+        final int granularity = 2;
+        final int unit = TypedValue.COMPLEX_UNIT_SP;
+
+        final T granularityView = getNewAutoSizeViewInstance();
+        ((AutoSizeableTextView) granularityView).setAutoSizeTextTypeUniformWithConfiguration(
+                minTextSize, maxTextSize, granularity, unit);
+
+        final T presetView = getNewAutoSizeViewInstance();
+        ((AutoSizeableTextView) presetView).setAutoSizeTextTypeUniformWithPresetSizes(
+                new int[]{minTextSize, 12, 14, 16, 18, maxTextSize}, unit);
+
+        // The TextViews have been configured differently but the end result should be nearly
+        // identical.
+        final int expectedAutoSizeType = AppCompatTextView.AUTO_SIZE_TEXT_TYPE_UNIFORM;
+        assertEquals(expectedAutoSizeType,
+                ((AutoSizeableTextView) granularityView).getAutoSizeTextType());
+        assertEquals(expectedAutoSizeType,
+                ((AutoSizeableTextView) presetView).getAutoSizeTextType());
+
+        final int expectedMinTextSizeInPx = Math.round(
+                TypedValue.applyDimension(unit, minTextSize, dm));
+        assertEquals(expectedMinTextSizeInPx,
+                ((AutoSizeableTextView) granularityView).getAutoSizeMinTextSize());
+        assertEquals(expectedMinTextSizeInPx,
+                ((AutoSizeableTextView) presetView).getAutoSizeMinTextSize());
+
+        final int expectedMaxTextSizeInPx = Math.round(
+                TypedValue.applyDimension(unit, maxTextSize, dm));
+        assertEquals(expectedMaxTextSizeInPx,
+                ((AutoSizeableTextView) granularityView).getAutoSizeMaxTextSize());
+        assertEquals(expectedMaxTextSizeInPx,
+                ((AutoSizeableTextView) presetView).getAutoSizeMaxTextSize());
+
+        // Configured with granularity.
+        assertEquals(Math.round(TypedValue.applyDimension(unit, granularity, dm)),
+                ((AutoSizeableTextView) granularityView).getAutoSizeStepGranularity());
+        // Configured with preset values, there is no granularity.
+        assertEquals(-1,
+                ((AutoSizeableTextView) presetView).getAutoSizeStepGranularity());
+
+        // Both TextViews generate exactly the same sizes in pixels to choose from when auto-sizing.
+        assertArrayEquals(
+                ((AutoSizeableTextView) granularityView).getAutoSizeTextAvailableSizes(),
+                ((AutoSizeableTextView) presetView).getAutoSizeTextAvailableSizes());
+
+        final String someText = "This is a string";
+        final LinearLayout.LayoutParams layoutParams = new LinearLayout.LayoutParams(
+                500, 500);
+        // Configure identically and attach to layout.
+        mActivityTestRule.runOnUiThread(new Runnable() {
+            @Override
+            public void run() {
+                granularityView.setLayoutParams(layoutParams);
+                presetView.setLayoutParams(layoutParams);
+
+                LinearLayout ll = mActivity.findViewById(R.id.container);
+                ll.removeAllViews();
+                ll.addView(granularityView);
+                ll.addView(presetView);
+
+                granularityView.setText(someText);
+                presetView.setText(someText);
+            }
+        });
+        mInstrumentation.waitForIdleSync();
+
+        assertEquals(granularityView.getTextSize(), presetView.getTextSize(), 0f);
+    }
+
+    @Test
+    @MediumTest
+    public void testAutoSizeCallers_setHeight() throws Throwable {
+        final T autoSizeView = prepareAndRetrieveAutoSizeTestData(
+                R.id.view_autosize_uniform, true);
+        // Do not force exact height only.
+        final LinearLayout.LayoutParams layoutParams = new LinearLayout.LayoutParams(
+                200,
+                LinearLayout.LayoutParams.WRAP_CONTENT);
+        mActivityTestRule.runOnUiThread(new Runnable() {
+            @Override
+            public void run() {
+                autoSizeView.setLayoutParams(layoutParams);
+            }
+        });
+        mInstrumentation.waitForIdleSync();
+        final float initialTextSize = autoSizeView.getTextSize();
+        mActivityTestRule.runOnUiThread(new Runnable() {
+            @Override
+            public void run() {
+                autoSizeView.setHeight(autoSizeView.getHeight() / 4);
+            }
+        });
+        mInstrumentation.waitForIdleSync();
+
+        assertTrue(autoSizeView.getTextSize() < initialTextSize);
+    }
+
+    @Test
+    @MediumTest
+    public void testAutoSizeCallers_setCompoundDrawables() throws Throwable {
+        final T autoSizeView = prepareAndRetrieveAutoSizeTestData(R.id.view_autosize_uniform,
+                false);
+        final float initialTextSize = autoSizeView.getTextSize();
+        final Drawable drawable = ResourcesCompat.getDrawable(mActivity.getResources(),
+                R.drawable.test_drawable_red, null);
+        drawable.setBounds(0, 0, autoSizeView.getWidth() / 3, autoSizeView.getHeight() / 3);
+        mActivityTestRule.runOnUiThread(new Runnable() {
+            @Override
+            public void run() {
+                autoSizeView.setCompoundDrawables(drawable, drawable, drawable, drawable);
+            }
+        });
+        mInstrumentation.waitForIdleSync();
+
+        assertTrue(autoSizeView.getTextSize() < initialTextSize);
+    }
+
+    @Test
+    @MediumTest
+    public void testAutoSizeCallers_setCompoundDrawablesRelative() throws Throwable {
+        if (Build.VERSION.SDK_INT >= 17) {
+            final T autoSizeView = prepareAndRetrieveAutoSizeTestData(
+                    R.id.view_autosize_uniform, false);
+            final float initialTextSize = autoSizeView.getTextSize();
+            final Drawable drawable = ResourcesCompat.getDrawable(mActivity.getResources(),
+                    R.drawable.test_drawable_red, null);
+            drawable.setBounds(0, 0, autoSizeView.getWidth() / 3,
+                    autoSizeView.getHeight() / 3);
+            mActivityTestRule.runOnUiThread(new Runnable() {
+                @Override
+                public void run() {
+                    if (Build.VERSION.SDK_INT >= 17) {
+                        autoSizeView.setCompoundDrawablesRelative(
+                                drawable, drawable, drawable, drawable);
+                    }
+                }
+            });
+            mInstrumentation.waitForIdleSync();
+
+            assertTrue(autoSizeView.getTextSize() < initialTextSize);
+        }
+    }
+
+    @Test
+    @MediumTest
+    public void testAutoSizeCallers_setCompoundDrawablePadding() throws Throwable {
+        final T autoSizeView = prepareAndRetrieveAutoSizeTestData(R.id.view_autosize_uniform,
+                false);
+        // Prepare a larger layout in order not to hit the min value easily.
+        mActivityTestRule.runOnUiThread(new Runnable() {
+            @Override
+            public void run() {
+                autoSizeView.setWidth(autoSizeView.getWidth() * 2);
+                autoSizeView.setHeight(autoSizeView.getHeight() * 2);
+            }
+        });
+        mInstrumentation.waitForIdleSync();
+        // Setup the drawables before setting their padding in order to modify the available
+        // space and trigger a resize.
+        final Drawable drawable = ResourcesCompat.getDrawable(mActivity.getResources(),
+                R.drawable.test_drawable_red, null);
+        drawable.setBounds(0, 0, autoSizeView.getWidth() / 4, autoSizeView.getHeight() / 4);
+        mActivityTestRule.runOnUiThread(new Runnable() {
+            @Override
+            public void run() {
+                autoSizeView.setCompoundDrawables(
+                        drawable, drawable, drawable, drawable);
+            }
+        });
+        mInstrumentation.waitForIdleSync();
+        final float initialTextSize = autoSizeView.getTextSize();
+        mActivityTestRule.runOnUiThread(new Runnable() {
+            @Override
+            public void run() {
+                autoSizeView.setCompoundDrawablePadding(
+                        autoSizeView.getCompoundDrawablePadding() + 10);
+            }
+        });
+        mInstrumentation.waitForIdleSync();
+
+        assertTrue(autoSizeView.getTextSize() < initialTextSize);
+    }
+
+    @Test
+    @MediumTest
+    public void testAutoSizeCallers_setPadding() throws Throwable {
+        final T autoSizeView = prepareAndRetrieveAutoSizeTestData(R.id.view_autosize_uniform,
+                false);
+        final float initialTextSize = autoSizeView.getTextSize();
+        mActivityTestRule.runOnUiThread(new Runnable() {
+            @Override
+            public void run() {
+                autoSizeView.setPadding(
+                        autoSizeView.getWidth() / 3, autoSizeView.getHeight() / 3,
+                        autoSizeView.getWidth() / 3, autoSizeView.getHeight() / 3);
+            }
+        });
+        mInstrumentation.waitForIdleSync();
+
+        assertTrue(autoSizeView.getTextSize() < initialTextSize);
+    }
+
+    @Test
+    @MediumTest
+    public void testAutoSizeCallers_setPaddingRelative() throws Throwable {
+        if (Build.VERSION.SDK_INT >= 16) {
+            final T autoSizeView = prepareAndRetrieveAutoSizeTestData(
+                    R.id.view_autosize_uniform, false);
+            final float initialTextSize = autoSizeView.getTextSize();
+
+            mActivityTestRule.runOnUiThread(new Runnable() {
+                @Override
+                public void run() {
+                    if (Build.VERSION.SDK_INT > 16) {
+                        autoSizeView.setPaddingRelative(
+                                autoSizeView.getWidth() / 3, autoSizeView.getHeight() / 3,
+                                autoSizeView.getWidth() / 3, autoSizeView.getHeight() / 3);
+                    }
+                }
+            });
+            mInstrumentation.waitForIdleSync();
+
+            assertTrue(autoSizeView.getTextSize() < initialTextSize);
+        }
+    }
+
+    @Test
+    @MediumTest
+    public void testAutoSizeCallers_setTypeface() throws Throwable {
+        final T autoSizeView = prepareAndRetrieveAutoSizeTestData(R.id.view_autosize_uniform,
+                false);
+        mActivityTestRule.runOnUiThread(new Runnable() {
+            @Override
+            public void run() {
+                autoSizeView.setText("The typeface change needs a bit more text then "
+                        + "the default used for this batch of tests in order to get to resize text."
+                        + " The resize function is always called but even with different typefaces "
+                        + "there may not be a need to resize text because it just fits. The longer "
+                        + "the text, the higher the chance for a resize. And here is yet another "
+                        + "sentence to make sure this test is not flaky. Not flaky at all.");
+            }
+        });
+        mInstrumentation.waitForIdleSync();
+        final float initialTextSize = autoSizeView.getTextSize();
+
+        mActivityTestRule.runOnUiThread(new Runnable() {
+            @Override
+            public void run() {
+                Typeface differentTypeface = Typeface.MONOSPACE;
+                if (autoSizeView.getTypeface() == Typeface.MONOSPACE) {
+                    differentTypeface = Typeface.SANS_SERIF;
+                }
+                autoSizeView.setTypeface(differentTypeface);
+            }
+        });
+        mInstrumentation.waitForIdleSync();
+        final float changedTextSize = autoSizeView.getTextSize();
+
+        // Don't really know if it is larger or smaller (depends on the typeface chosen above),
+        // but it should definitely have changed.
+        assertNotEquals(initialTextSize, changedTextSize, 0f);
+
+        mActivityTestRule.runOnUiThread(new Runnable() {
+            @Override
+            public void run() {
+                autoSizeView.setTypeface(autoSizeView.getTypeface());
+            }
+        });
+        mInstrumentation.waitForIdleSync();
+
+        assertEquals(changedTextSize, autoSizeView.getTextSize(), 0f);
+    }
+
+    @Test
+    @MediumTest
+    public void testAutoSizeCallers_setLetterSpacing() throws Throwable {
+        if (Build.VERSION.SDK_INT >= 21) {
+            final T autoSizeView = prepareAndRetrieveAutoSizeTestData(
+                    R.id.view_autosize_uniform, false);
+            final float initialTextSize = autoSizeView.getTextSize();
+
+            mActivityTestRule.runOnUiThread(new Runnable() {
+                @Override
+                public void run() {
+                    if (Build.VERSION.SDK_INT >= 21) {
+                        autoSizeView.setLetterSpacing(
+                                autoSizeView.getLetterSpacing() * 1.5f + 4.5f);
+                    }
+                }
+            });
+            mInstrumentation.waitForIdleSync();
+            final float changedTextSize = autoSizeView.getTextSize();
+
+            assertTrue(changedTextSize < initialTextSize);
+
+            mActivityTestRule.runOnUiThread(new Runnable() {
+                @Override
+                public void run() {
+                    if (Build.VERSION.SDK_INT >= 21) {
+                        autoSizeView.setLetterSpacing(autoSizeView.getLetterSpacing());
+                    }
+                }
+            });
+            mInstrumentation.waitForIdleSync();
+
+            assertEquals(changedTextSize, autoSizeView.getTextSize(), 0f);
+        }
+    }
+
+    @Test
+    @MediumTest
+    public void testAutoSizeCallers_setMaxHeight() throws Throwable {
+        final T autoSizeView = prepareAndRetrieveAutoSizeTestData(R.id.view_autosize_uniform,
+                true);
+        // Do not force exact height only.
+        final LinearLayout.LayoutParams layoutParams = new LinearLayout.LayoutParams(
+                200,
+                LinearLayout.LayoutParams.WRAP_CONTENT);
+        mActivityTestRule.runOnUiThread(new Runnable() {
+            @Override
+            public void run() {
+                autoSizeView.setLayoutParams(layoutParams);
+            }
+        });
+        mInstrumentation.waitForIdleSync();
+        final float initialTextSize = autoSizeView.getTextSize();
+        mActivityTestRule.runOnUiThread(new Runnable() {
+            @Override
+            public void run() {
+                autoSizeView.setMaxHeight(autoSizeView.getHeight() / 4);
+            }
+        });
+        mInstrumentation.waitForIdleSync();
+
+        assertTrue(autoSizeView.getTextSize() < initialTextSize);
+    }
+
+    @Test
+    @MediumTest
+    public void testAutoSizeCallers_setMaxWidth() throws Throwable {
+        final T autoSizeView = prepareAndRetrieveAutoSizeTestData(R.id.view_autosize_uniform,
+                true);
+        // Do not force exact width only.
+        final LinearLayout.LayoutParams layoutParams = new LinearLayout.LayoutParams(
+                LinearLayout.LayoutParams.WRAP_CONTENT,
+                200);
+        mActivityTestRule.runOnUiThread(new Runnable() {
+            @Override
+            public void run() {
+                autoSizeView.setLayoutParams(layoutParams);
+            }
+        });
+        mInstrumentation.waitForIdleSync();
+        final float initialTextSize = autoSizeView.getTextSize();
+        mActivityTestRule.runOnUiThread(new Runnable() {
+            @Override
+            public void run() {
+                autoSizeView.setMaxWidth(autoSizeView.getWidth() / 4);
+            }
+        });
+        mInstrumentation.waitForIdleSync();
+
+        assertTrue(autoSizeView.getTextSize() != initialTextSize);
+    }
+
+    @Test
+    @MediumTest
+    public void testAutoSizeCallers_setWidth() throws Throwable {
+        final T autoSizeView = prepareAndRetrieveAutoSizeTestData(R.id.view_autosize_uniform,
+                true);
+        // Do not force exact width only.
+        final LinearLayout.LayoutParams layoutParams = new LinearLayout.LayoutParams(
+                LinearLayout.LayoutParams.WRAP_CONTENT,
+                200);
+        mActivityTestRule.runOnUiThread(new Runnable() {
+            @Override
+            public void run() {
+                autoSizeView.setLayoutParams(layoutParams);
+            }
+        });
+        mInstrumentation.waitForIdleSync();
+
+        final float initialTextSize = autoSizeView.getTextSize();
+        mActivityTestRule.runOnUiThread(new Runnable() {
+            @Override
+            public void run() {
+                autoSizeView.setWidth(autoSizeView.getWidth() / 4);
+            }
+        });
+        mInstrumentation.waitForIdleSync();
+
+        assertTrue(autoSizeView.getTextSize() != initialTextSize);
+    }
+
+    @Test
+    @MediumTest
+    public void testAutoSizeCallers_setTextSizeIsNoOp() throws Throwable {
+        final T autoSizeView = prepareAndRetrieveAutoSizeTestData(R.id.view_autosize_uniform,
+                false);
+        final float initialTextSize = autoSizeView.getTextSize();
+
+        mActivityTestRule.runOnUiThread(new Runnable() {
+            @Override
+            public void run() {
+                autoSizeView.setTextSize(initialTextSize + 123f);
+            }
+        });
+        mInstrumentation.waitForIdleSync();
+
+        assertEquals(initialTextSize, autoSizeView.getTextSize(), 0f);
+    }
+
+    @Test
+    @MediumTest
+    public void testAutoSizeCallers_setHorizontallyScrolling() throws Throwable {
+        final T autoSizeView = prepareAndRetrieveAutoSizeTestData(R.id.view_autosize_uniform,
+                false);
+        // Horizontal scrolling is expected to be deactivated for this test.
+        final float initialTextSize = autoSizeView.getTextSize();
+        mActivityTestRule.runOnUiThread(new Runnable() {
+            @Override
+            public void run() {
+                autoSizeView.setHorizontallyScrolling(true);
+            }
+        });
+        mInstrumentation.waitForIdleSync();
+        assertTrue(autoSizeView.getTextSize() > initialTextSize);
+
+        mActivityTestRule.runOnUiThread(new Runnable() {
+            @Override
+            public void run() {
+                autoSizeView.setHorizontallyScrolling(false);
+            }
+        });
+        mInstrumentation.waitForIdleSync();
+        assertEquals(initialTextSize, autoSizeView.getTextSize(), 0f);
+    }
+
+    @Test
+    @MediumTest
+    public void testAutoSize_setEllipsize() throws Throwable {
+        final T autoSizeView = mActivity.findViewById(R.id.view_autosize_uniform_predef_sizes);
+        final int initialAutoSizeType = ((AutoSizeableTextView) autoSizeView).getAutoSizeTextType();
+        final int initialMinTextSize =
+                ((AutoSizeableTextView) autoSizeView).getAutoSizeMinTextSize();
+        final int initialMaxTextSize =
+                ((AutoSizeableTextView) autoSizeView).getAutoSizeMaxTextSize();
+        final int initialAutoSizeGranularity =
+                ((AutoSizeableTextView) autoSizeView).getAutoSizeStepGranularity();
+        final int initialSizes =
+                ((AutoSizeableTextView) autoSizeView).getAutoSizeTextAvailableSizes().length;
+
+        assertEquals(null, autoSizeView.getEllipsize());
+        // Verify styled attributes.
+        assertEquals(TextViewCompat.AUTO_SIZE_TEXT_TYPE_UNIFORM, initialAutoSizeType);
+        assertNotEquals(-1, initialMinTextSize);
+        assertNotEquals(-1, initialMaxTextSize);
+        // Because this TextView has been configured to use predefined sizes.
+        assertEquals(-1, initialAutoSizeGranularity);
+        assertNotEquals(0, initialSizes);
+
+        final TextUtils.TruncateAt newEllipsizeValue = TextUtils.TruncateAt.END;
+        mActivityTestRule.runOnUiThread(new Runnable() {
+            @Override
+            public void run() {
+                autoSizeView.setEllipsize(newEllipsizeValue);
+            }
+        });
+        mInstrumentation.waitForIdleSync();
+        assertEquals(newEllipsizeValue, autoSizeView.getEllipsize());
+        // Beside the ellipsis no auto-size attribute has changed.
+        assertEquals(initialAutoSizeType,
+                ((AutoSizeableTextView) autoSizeView).getAutoSizeTextType());
+        assertEquals(initialMinTextSize,
+                ((AutoSizeableTextView) autoSizeView).getAutoSizeMinTextSize());
+        assertEquals(initialMaxTextSize,
+                ((AutoSizeableTextView) autoSizeView).getAutoSizeMaxTextSize());
+        assertEquals(initialAutoSizeGranularity,
+                ((AutoSizeableTextView) autoSizeView).getAutoSizeStepGranularity());
+        assertEquals(initialSizes,
+                ((AutoSizeableTextView) autoSizeView).getAutoSizeTextAvailableSizes().length);
+    }
+
+    @Test
+    @MediumTest
+    public void testEllipsize_setAutoSize() throws Throwable {
+        final T view = mActivity.findViewById(R.id.view_text);
+        final TextUtils.TruncateAt newEllipsizeValue = TextUtils.TruncateAt.END;
+        mActivityTestRule.runOnUiThread(new Runnable() {
+            @Override
+            public void run() {
+                view.setEllipsize(newEllipsizeValue);
+            }
+        });
+        mInstrumentation.waitForIdleSync();
+        assertEquals(newEllipsizeValue, view.getEllipsize());
+        assertEquals(TextViewCompat.AUTO_SIZE_TEXT_TYPE_NONE,
+                ((AutoSizeableTextView) view).getAutoSizeTextType());
+        assertEquals(-1, ((AutoSizeableTextView) view).getAutoSizeMinTextSize());
+        assertEquals(-1, ((AutoSizeableTextView) view).getAutoSizeMaxTextSize());
+        assertEquals(-1, ((AutoSizeableTextView) view).getAutoSizeStepGranularity());
+        assertEquals(0,
+                ((AutoSizeableTextView) view).getAutoSizeTextAvailableSizes().length);
+
+        mActivityTestRule.runOnUiThread(new Runnable() {
+            @Override
+            public void run() {
+                ((AutoSizeableTextView) view).setAutoSizeTextTypeWithDefaults(
+                        TextViewCompat.AUTO_SIZE_TEXT_TYPE_UNIFORM);
+            }
+        });
+        mInstrumentation.waitForIdleSync();
+        assertEquals(newEllipsizeValue, view.getEllipsize());
+        assertEquals(TextViewCompat.AUTO_SIZE_TEXT_TYPE_UNIFORM,
+                ((AutoSizeableTextView) view).getAutoSizeTextType());
+        // The auto-size defaults have been used.
+        assertNotEquals(-1, ((AutoSizeableTextView) view).getAutoSizeMinTextSize());
+        assertNotEquals(-1, ((AutoSizeableTextView) view).getAutoSizeMaxTextSize());
+        assertNotEquals(-1, ((AutoSizeableTextView) view).getAutoSizeStepGranularity());
+        assertNotEquals(0,
+                ((AutoSizeableTextView) view).getAutoSizeTextAvailableSizes().length);
+    }
+
+    @Test
+    @MediumTest
+    public void testAutoSizeUniform_obtainStyledAttributesUsingPredefinedSizes() {
+        DisplayMetrics m = mActivity.getResources().getDisplayMetrics();
+        final T autoSizeViewUniform = mActivity.findViewById(
+                R.id.view_autosize_uniform_predef_sizes);
+
+        // In arrays.xml predefined the step sizes as: 5px, 11dip, 19sp, 29pt, 43mm and 53in.
+        // TypedValue can not use the math library and instead rounds the value by adding
+        // 0.5f when obtaining styled attributes. Check TypedValue#complexToDimensionPixelSize(...)
+        int[] expectedSizesInPx = new int[] {
+                Math.round(TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_PX, 5f, m)),
+                Math.round(TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 11f, m)),
+                Math.round(TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_SP, 19f, m)),
+                Math.round(TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_PT, 29f, m)),
+                Math.round(TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_MM, 43f, m)),
+                Math.round(TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_IN, 53f, m))};
+
+        boolean containsValueFromExpectedSizes = false;
+        final int textSize = (int) autoSizeViewUniform.getTextSize();
+        for (int i = 0; i < expectedSizesInPx.length; i++) {
+            if (expectedSizesInPx[i] == textSize) {
+                containsValueFromExpectedSizes = true;
+                break;
+            }
+        }
+        assertTrue(containsValueFromExpectedSizes);
+    }
+
+    @Test
+    @MediumTest
+    public void testAutoSizeUniform_obtainStyledAttributesPredefinedSizesFiltering() {
+        T autoSizeViewUniform = mActivity.findViewById(
+                R.id.view_autosize_uniform_predef_sizes_redundant_values);
+
+        // In arrays.xml predefined the step sizes as: 40px, 10px, 10px, 10px, 0dp.
+        final int[] expectedSizes = new int[] {10, 40};
+        assertArrayEquals(expectedSizes,
+                ((AutoSizeableTextView) autoSizeViewUniform).getAutoSizeTextAvailableSizes());
+    }
+
+    @Test
+    @MediumTest
+    public void testAutoSizeUniform_predefinedSizesFilteringAndSorting() throws Throwable {
+        final T view = mActivity.findViewById(R.id.view_text);
+        assertEquals(TextViewCompat.AUTO_SIZE_TEXT_TYPE_NONE,
+                ((AutoSizeableTextView) view).getAutoSizeTextType());
+
+        final int[] predefinedSizes = new int[] {400, 0, 10, 40, 10, 10, 0, 0};
+        mActivityTestRule.runOnUiThread(new Runnable() {
+            @Override
+            public void run() {
+                ((AutoSizeableTextView) view).setAutoSizeTextTypeUniformWithPresetSizes(
+                        predefinedSizes, TypedValue.COMPLEX_UNIT_PX);
+            }
+        });
+        mInstrumentation.waitForIdleSync();
+        assertArrayEquals(new int[] {10, 40, 400},
+                ((AutoSizeableTextView) view).getAutoSizeTextAvailableSizes());
+    }
+
+    @Test(expected = NullPointerException.class)
+    @SmallTest
+    public void testAutoSizeUniform_predefinedSizesNullArray() throws Throwable {
+        final T view = mActivity.findViewById(R.id.view_text);
+        assertEquals(TextViewCompat.AUTO_SIZE_TEXT_TYPE_NONE,
+                ((AutoSizeableTextView) view).getAutoSizeTextType());
+
+        final int[] predefinedSizes = null;
+        mActivityTestRule.runOnUiThread(new Runnable() {
+            @Override
+            public void run() {
+                ((AutoSizeableTextView) view).setAutoSizeTextTypeUniformWithPresetSizes(
+                        predefinedSizes, TypedValue.COMPLEX_UNIT_PX);
+            }
+        });
+        mInstrumentation.waitForIdleSync();
+    }
+
+    @Test
+    @MediumTest
+    public void testAutoSizeUniform_predefinedSizesEmptyArray() throws Throwable {
+        final T view = mActivity.findViewById(R.id.view_text);
+        assertEquals(TextViewCompat.AUTO_SIZE_TEXT_TYPE_NONE,
+                ((AutoSizeableTextView) view).getAutoSizeTextType());
+
+        mActivityTestRule.runOnUiThread(new Runnable() {
+            @Override
+            public void run() {
+                ((AutoSizeableTextView) view).setAutoSizeTextTypeWithDefaults(
+                        TextViewCompat.AUTO_SIZE_TEXT_TYPE_UNIFORM);
+            }
+        });
+        mInstrumentation.waitForIdleSync();
+
+        final int[] defaultSizes = ((AutoSizeableTextView) view).getAutoSizeTextAvailableSizes();
+        assertNotNull(defaultSizes);
+        assertTrue(defaultSizes.length > 0);
+
+        final int[] predefinedSizes = new int[0];
+        mActivityTestRule.runOnUiThread(new Runnable() {
+            @Override
+            public void run() {
+                ((AutoSizeableTextView) view).setAutoSizeTextTypeUniformWithPresetSizes(
+                        predefinedSizes, TypedValue.COMPLEX_UNIT_PX);
+            }
+        });
+        mInstrumentation.waitForIdleSync();
+
+        final int[] newSizes = ((AutoSizeableTextView) view).getAutoSizeTextAvailableSizes();
+        assertNotNull(defaultSizes);
+        assertArrayEquals(defaultSizes, newSizes);
+    }
+
+    @Test
+    @MediumTest
+    public void testAutoSizeUniform_buildsSizes() throws Throwable {
+        final T autoSizeViewUniform = mActivity.findViewById(R.id.view_autosize_uniform);
+
+        // Verify that the interval limits are both included.
+        mActivityTestRule.runOnUiThread(new Runnable() {
+            @Override
+            public void run() {
+                ((AutoSizeableTextView) autoSizeViewUniform)
+                        .setAutoSizeTextTypeUniformWithConfiguration(10, 20, 2,
+                                TypedValue.COMPLEX_UNIT_PX);
+            }
+        });
+        mInstrumentation.waitForIdleSync();
+        assertArrayEquals(
+                new int[] {10, 12, 14, 16, 18, 20},
+                ((AutoSizeableTextView) autoSizeViewUniform).getAutoSizeTextAvailableSizes());
+
+        mActivityTestRule.runOnUiThread(new Runnable() {
+            @Override
+            public void run() {
+                ((AutoSizeableTextView) autoSizeViewUniform)
+                        .setAutoSizeTextTypeUniformWithConfiguration(
+                                ((AutoSizeableTextView) autoSizeViewUniform)
+                                        .getAutoSizeMinTextSize(),
+                                19,
+                                ((AutoSizeableTextView) autoSizeViewUniform)
+                                        .getAutoSizeStepGranularity(),
+                                TypedValue.COMPLEX_UNIT_PX);
+            }
+        });
+        mInstrumentation.waitForIdleSync();
+        assertArrayEquals(
+                new int[] {10, 12, 14, 16, 18},
+                ((AutoSizeableTextView) autoSizeViewUniform).getAutoSizeTextAvailableSizes());
+
+        mActivityTestRule.runOnUiThread(new Runnable() {
+            @Override
+            public void run() {
+                ((AutoSizeableTextView) autoSizeViewUniform)
+                        .setAutoSizeTextTypeUniformWithConfiguration(
+                                ((AutoSizeableTextView) autoSizeViewUniform)
+                                        .getAutoSizeMinTextSize(),
+                                21,
+                                ((AutoSizeableTextView) autoSizeViewUniform)
+                                        .getAutoSizeStepGranularity(),
+                                TypedValue.COMPLEX_UNIT_PX);
+            }
+        });
+        mInstrumentation.waitForIdleSync();
+        assertArrayEquals(
+                new int[] {10, 12, 14, 16, 18, 20},
+                ((AutoSizeableTextView) autoSizeViewUniform).getAutoSizeTextAvailableSizes());
+    }
+
+    @Test
+    @MediumTest
+    public void testAutoSizeUniform_getSetAutoSizeTextDefaults() {
+        final T autoSizeView = getNewAutoSizeViewInstance();
+        assertEquals(TextViewCompat.AUTO_SIZE_TEXT_TYPE_NONE,
+                ((AutoSizeableTextView) autoSizeView).getAutoSizeTextType());
+        // Min/Max/Granularity values for auto-sizing are 0 because they are not used.
+        assertEquals(-1, ((AutoSizeableTextView) autoSizeView).getAutoSizeMinTextSize());
+        assertEquals(-1, ((AutoSizeableTextView) autoSizeView).getAutoSizeMaxTextSize());
+        assertEquals(-1,
+                ((AutoSizeableTextView) autoSizeView).getAutoSizeStepGranularity());
+
+        ((AutoSizeableTextView) autoSizeView).setAutoSizeTextTypeWithDefaults(
+                TextViewCompat.AUTO_SIZE_TEXT_TYPE_UNIFORM);
+        assertEquals(TextViewCompat.AUTO_SIZE_TEXT_TYPE_UNIFORM,
+                ((AutoSizeableTextView) autoSizeView).getAutoSizeTextType());
+        // Min/Max default values for auto-sizing XY have been loaded.
+        final int minSize = ((AutoSizeableTextView) autoSizeView).getAutoSizeMinTextSize();
+        final int maxSize = ((AutoSizeableTextView) autoSizeView).getAutoSizeMaxTextSize();
+        assertTrue(0 < minSize);
+        assertTrue(minSize < maxSize);
+        assertNotEquals(0,
+                ((AutoSizeableTextView) autoSizeView).getAutoSizeStepGranularity());
+
+        ((AutoSizeableTextView) autoSizeView)
+                .setAutoSizeTextTypeWithDefaults(TextViewCompat.AUTO_SIZE_TEXT_TYPE_NONE);
+        assertEquals(TextViewCompat.AUTO_SIZE_TEXT_TYPE_NONE,
+                ((AutoSizeableTextView) autoSizeView).getAutoSizeTextType());
+        // Min/Max values for auto-sizing XY have been cleared.
+        assertEquals(-1, ((AutoSizeableTextView) autoSizeView).getAutoSizeMinTextSize());
+        assertEquals(-1, ((AutoSizeableTextView) autoSizeView).getAutoSizeMaxTextSize());
+        assertEquals(-1,
+                ((AutoSizeableTextView) autoSizeView).getAutoSizeStepGranularity());
+    }
+
+    @Test
+    @MediumTest
+    public void testAutoSizeUniform_getSetAutoSizeStepGranularity() {
+        final T autoSizeView = getNewAutoSizeViewInstance();
+        assertEquals(TextViewCompat.AUTO_SIZE_TEXT_TYPE_NONE,
+                ((AutoSizeableTextView) autoSizeView).getAutoSizeTextType());
+        final int initialValue = -1;
+        assertEquals(initialValue,
+                ((AutoSizeableTextView) autoSizeView).getAutoSizeStepGranularity());
+
+        ((AutoSizeableTextView) autoSizeView).setAutoSizeTextTypeWithDefaults(
+                TextViewCompat.AUTO_SIZE_TEXT_TYPE_UNIFORM);
+        assertEquals(TextViewCompat.AUTO_SIZE_TEXT_TYPE_UNIFORM,
+                ((AutoSizeableTextView) autoSizeView).getAutoSizeTextType());
+        final int defaultValue = 1; // 1px.
+        // If the auto-size type is AUTO_SIZE_TEXT_TYPE_UNIFORM then it means autoSizeView went
+        // through the auto-size setup and given that 0 is an invalid value it changed it to the
+        // default.
+        assertEquals(defaultValue,
+                ((AutoSizeableTextView) autoSizeView).getAutoSizeStepGranularity());
+
+        final int newValue = 33;
+        ((AutoSizeableTextView) autoSizeView).setAutoSizeTextTypeUniformWithConfiguration(
+                ((AutoSizeableTextView) autoSizeView).getAutoSizeMinTextSize(),
+                ((AutoSizeableTextView) autoSizeView).getAutoSizeMaxTextSize(),
+                newValue,
+                TypedValue.COMPLEX_UNIT_PX);
+        assertEquals(newValue, ((AutoSizeableTextView) autoSizeView).getAutoSizeStepGranularity());
+    }
+
+    @Test
+    @MediumTest
+    public void testAutoSizeUniform_getSetAutoSizeMinTextSize() {
+        final T autoSizeView = getNewAutoSizeViewInstance();
+        ((AutoSizeableTextView) autoSizeView).setAutoSizeTextTypeWithDefaults(
+                TextViewCompat.AUTO_SIZE_TEXT_TYPE_UNIFORM);
+        assertEquals(TextViewCompat.AUTO_SIZE_TEXT_TYPE_UNIFORM,
+                ((AutoSizeableTextView) autoSizeView).getAutoSizeTextType());
+        final int minSize = ((AutoSizeableTextView) autoSizeView).getAutoSizeMinTextSize();
+        assertNotEquals(0, minSize);
+        final int maxSize = ((AutoSizeableTextView) autoSizeView).getAutoSizeMaxTextSize();
+        assertNotEquals(0, maxSize);
+
+        // This is just a test check to verify the next assertions. If this fails it is a problem
+        // of this test setup (we need at least 2 units).
+        assertTrue((maxSize - minSize) > 1);
+        final int newMinSize = maxSize - 1;
+        ((AutoSizeableTextView) autoSizeView).setAutoSizeTextTypeUniformWithConfiguration(
+                newMinSize,
+                ((AutoSizeableTextView) autoSizeView).getAutoSizeMaxTextSize(),
+                ((AutoSizeableTextView) autoSizeView).getAutoSizeStepGranularity(),
+                TypedValue.COMPLEX_UNIT_PX);
+
+        assertEquals(newMinSize, ((AutoSizeableTextView) autoSizeView).getAutoSizeMinTextSize());
+        // Max size has not changed.
+        assertEquals(maxSize, ((AutoSizeableTextView) autoSizeView).getAutoSizeMaxTextSize());
+
+        ((AutoSizeableTextView) autoSizeView).setAutoSizeTextTypeUniformWithConfiguration(
+                newMinSize,
+                newMinSize + 10,
+                ((AutoSizeableTextView) autoSizeView).getAutoSizeStepGranularity(),
+                TypedValue.COMPLEX_UNIT_SP);
+
+        // It does not matter which unit has been used to set the min size, the getter always
+        // returns it in pixels.
+        assertEquals(Math.round(TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_SP,
+                newMinSize, mActivity.getResources().getDisplayMetrics())),
+                ((AutoSizeableTextView) autoSizeView).getAutoSizeMinTextSize());
+    }
+
+    @Test(expected = IllegalArgumentException.class)
+    @SmallTest
+    public void testAutoSizeUniform_throwsException_whenMaxLessThanMin() {
+        final T autoSizeView = getNewAutoSizeViewInstance();
+        ((AutoSizeableTextView) autoSizeView).setAutoSizeTextTypeUniformWithConfiguration(
+                10, 9, 1, TypedValue.COMPLEX_UNIT_SP);
+    }
+
+    @Test(expected = IllegalArgumentException.class)
+    @SmallTest
+    public void testAutoSizeUniform_throwsException_minLessThanZero() {
+        final T autoSizeView = getNewAutoSizeViewInstance();
+        ((AutoSizeableTextView) autoSizeView).setAutoSizeTextTypeUniformWithConfiguration(
+                -1, 9, 1, TypedValue.COMPLEX_UNIT_SP);
+    }
+
+    @Test(expected = IllegalArgumentException.class)
+    @SmallTest
+    public void testAutoSizeUniform_throwsException_maxLessThanZero() {
+        final T autoSizeView = getNewAutoSizeViewInstance();
+        ((AutoSizeableTextView) autoSizeView).setAutoSizeTextTypeUniformWithConfiguration(
+                10, -1, 1, TypedValue.COMPLEX_UNIT_SP);
+    }
+
+    @Test(expected = IllegalArgumentException.class)
+    @SmallTest
+    public void testAutoSizeUniform_throwsException_granularityLessThanZero() {
+        final T autoSizeView = getNewAutoSizeViewInstance();
+        ((AutoSizeableTextView) autoSizeView).setAutoSizeTextTypeUniformWithConfiguration(
+                10, 20, -1, TypedValue.COMPLEX_UNIT_SP);
+    }
+
+    @Test
+    @MediumTest
+    public void testAutoSizeUniform_getSetAutoSizeMaxTextSize() {
+        final T autoSizeView = getNewAutoSizeViewInstance();
+        ((AutoSizeableTextView) autoSizeView).setAutoSizeTextTypeWithDefaults(
+                TextViewCompat.AUTO_SIZE_TEXT_TYPE_UNIFORM);
+        assertEquals(TextViewCompat.AUTO_SIZE_TEXT_TYPE_UNIFORM,
+                ((AutoSizeableTextView) autoSizeView).getAutoSizeTextType());
+        final int minSize = ((AutoSizeableTextView) autoSizeView).getAutoSizeMinTextSize();
+        assertNotEquals(0, minSize);
+        final int maxSize = ((AutoSizeableTextView) autoSizeView).getAutoSizeMaxTextSize();
+        assertNotEquals(0, maxSize);
+
+        final int newMaxSize = maxSize + 11;
+        ((AutoSizeableTextView) autoSizeView).setAutoSizeTextTypeUniformWithConfiguration(
+                ((AutoSizeableTextView) autoSizeView).getAutoSizeMinTextSize(),
+                newMaxSize,
+                ((AutoSizeableTextView) autoSizeView).getAutoSizeStepGranularity(),
+                TypedValue.COMPLEX_UNIT_PX);
+
+        assertEquals(newMaxSize, ((AutoSizeableTextView) autoSizeView).getAutoSizeMaxTextSize());
+        // Min size has not changed.
+        assertEquals(minSize, ((AutoSizeableTextView) autoSizeView).getAutoSizeMinTextSize());
+        ((AutoSizeableTextView) autoSizeView).setAutoSizeTextTypeUniformWithConfiguration(
+                ((AutoSizeableTextView) autoSizeView).getAutoSizeMinTextSize(),
+                newMaxSize,
+                ((AutoSizeableTextView) autoSizeView).getAutoSizeStepGranularity(),
+                TypedValue.COMPLEX_UNIT_SP);
+        // It does not matter which unit has been used to set the max size, the getter always
+        // returns it in pixels.
+        assertEquals(Math.round(TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_SP,
+                newMaxSize, mActivity.getResources().getDisplayMetrics())),
+                ((AutoSizeableTextView) autoSizeView).getAutoSizeMaxTextSize());
+    }
+
+    @Test
+    @MediumTest
+    public void testAutoSizeCallers_setTextSizeChangesSizeWhenAutoSizeDisabled() throws Throwable {
+        final T autoSizeView = prepareAndRetrieveAutoSizeTestData(R.id.view_autosize_uniform,
+                false);
+        // Disable auto-size.
+        mActivityTestRule.runOnUiThread(new Runnable() {
+            @Override
+            public void run() {
+                ((AutoSizeableTextView) autoSizeView).setAutoSizeTextTypeWithDefaults(
+                        TextViewCompat.AUTO_SIZE_TEXT_TYPE_NONE);
+            }
+        });
+        mInstrumentation.waitForIdleSync();
+        final float newTextSizeInPx = 123f;
+        assertNotEquals(newTextSizeInPx, autoSizeView.getTextSize(), 0f);
+
+        mActivityTestRule.runOnUiThread(new Runnable() {
+            @Override
+            public void run() {
+                autoSizeView.setTextSize(TypedValue.COMPLEX_UNIT_PX, newTextSizeInPx);
+            }
+        });
+        mInstrumentation.waitForIdleSync();
+
+        assertEquals(newTextSizeInPx, autoSizeView.getTextSize(), 0f);
+    }
+
+    /**
+     * Some View attributes require non-fixed width and/or layout height. This function removes
+     * all other existing views from the layout leaving only one auto-size TextView (for exercising
+     * the auto-size behavior) which has been set up to suit the test needs.
+     *
+     * @param viewId The id of the view to prepare.
+     * @param shouldWrapLayoutContent Specifies if the layout params should wrap content
+     *
+     * @return a View configured for auto size tests.
+     */
+    private T prepareAndRetrieveAutoSizeTestData(final @IdRes int viewId,
+            final boolean shouldWrapLayoutContent) throws Throwable {
+        mActivityTestRule.runOnUiThread(new Runnable() {
+            @Override
+            public void run() {
+                LinearLayout ll = mActivity.findViewById(R.id.container);
+                T targetedView = mActivity.findViewById(viewId);
+                ll.removeAllViews();
+                ll.addView(targetedView);
+            }
+        });
+        mInstrumentation.waitForIdleSync();
+
+        final T view = mActivity.findViewById(viewId);
+        if (shouldWrapLayoutContent) {
+            // Do not force exact width or height.
+            final LinearLayout.LayoutParams layoutParams = new LinearLayout.LayoutParams(
+                    LinearLayout.LayoutParams.WRAP_CONTENT,
+                    LinearLayout.LayoutParams.WRAP_CONTENT);
+            mActivityTestRule.runOnUiThread(new Runnable() {
+                @Override
+                public void run() {
+                    view.setLayoutParams(layoutParams);
+                }
+            });
+            mInstrumentation.waitForIdleSync();
+        }
+
+        return view;
+    }
+
+    // Returns a new instance of the auto-sizable view for mActivity.
+    protected abstract T getNewAutoSizeViewInstance();
+}
diff --git a/v7/appcompat/tests/src/android/support/v7/widget/AppCompatButtonAutoSizeActivity.java b/v7/appcompat/tests/src/android/support/v7/widget/AppCompatButtonAutoSizeActivity.java
new file mode 100644
index 0000000..cd5c6a9
--- /dev/null
+++ b/v7/appcompat/tests/src/android/support/v7/widget/AppCompatButtonAutoSizeActivity.java
@@ -0,0 +1,31 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.support.v7.widget;
+
+import android.support.v7.appcompat.test.R;
+import android.support.v7.testutils.BaseTestActivity;
+
+/**
+ * This activity is used to test the auto-size feature of the {@link AppCompatButton}
+ * class.
+ */
+public class AppCompatButtonAutoSizeActivity extends BaseTestActivity {
+    @Override
+    protected int getContentViewLayoutResId() {
+        return R.layout.appcompat_button_autosize_activity;
+    }
+}
diff --git a/v7/appcompat/tests/src/android/support/v7/widget/AppCompatButtonAutoSizeTest.java b/v7/appcompat/tests/src/android/support/v7/widget/AppCompatButtonAutoSizeTest.java
new file mode 100644
index 0000000..cc54463
--- /dev/null
+++ b/v7/appcompat/tests/src/android/support/v7/widget/AppCompatButtonAutoSizeTest.java
@@ -0,0 +1,37 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.support.v7.widget;
+
+import android.support.test.filters.MediumTest;
+import android.support.test.runner.AndroidJUnit4;
+
+import org.junit.runner.RunWith;
+
+@MediumTest
+@RunWith(AndroidJUnit4.class)
+public class AppCompatButtonAutoSizeTest extends
+        AppCompatBaseAutoSizeTest<AppCompatButtonAutoSizeActivity, AppCompatButton> {
+
+    public AppCompatButtonAutoSizeTest() {
+        super(AppCompatButtonAutoSizeActivity.class);
+    }
+
+    @Override
+    protected AppCompatButton getNewAutoSizeViewInstance() {
+        return new AppCompatButton(mActivity);
+    }
+}
diff --git a/v7/appcompat/tests/src/android/support/v7/widget/AppCompatTextViewAutoSizeActivity.java b/v7/appcompat/tests/src/android/support/v7/widget/AppCompatTextViewAutoSizeActivity.java
index 17ff6a2..5736dd9 100644
--- a/v7/appcompat/tests/src/android/support/v7/widget/AppCompatTextViewAutoSizeActivity.java
+++ b/v7/appcompat/tests/src/android/support/v7/widget/AppCompatTextViewAutoSizeActivity.java
@@ -15,6 +15,7 @@
  */
 package android.support.v7.widget;
 
+import android.support.v7.appcompat.test.R;
 import android.support.v7.testutils.BaseTestActivity;
 
 /**
@@ -24,6 +25,6 @@
 public class AppCompatTextViewAutoSizeActivity extends BaseTestActivity {
     @Override
     protected int getContentViewLayoutResId() {
-        return android.support.v7.appcompat.test.R.layout.appcompat_textview_autosize_activity;
+        return R.layout.appcompat_textview_autosize_activity;
     }
 }
diff --git a/v7/appcompat/tests/src/android/support/v7/widget/AppCompatTextViewAutoSizeTest.java b/v7/appcompat/tests/src/android/support/v7/widget/AppCompatTextViewAutoSizeTest.java
index 95d4af3..45da1e4 100644
--- a/v7/appcompat/tests/src/android/support/v7/widget/AppCompatTextViewAutoSizeTest.java
+++ b/v7/appcompat/tests/src/android/support/v7/widget/AppCompatTextViewAutoSizeTest.java
@@ -17,286 +17,39 @@
 
 import static junit.framework.TestCase.assertEquals;
 
-import static org.junit.Assert.assertArrayEquals;
-import static org.junit.Assert.assertNotEquals;
-import static org.junit.Assert.assertNotNull;
 import static org.junit.Assert.assertTrue;
 
-import android.app.Instrumentation;
 import android.content.Context;
-import android.graphics.Typeface;
-import android.graphics.drawable.Drawable;
 import android.os.Build;
 import android.support.annotation.Nullable;
-import android.support.test.InstrumentationRegistry;
 import android.support.test.filters.MediumTest;
-import android.support.test.filters.SdkSuppress;
-import android.support.test.rule.ActivityTestRule;
 import android.support.test.runner.AndroidJUnit4;
-import android.support.v4.content.res.ResourcesCompat;
 import android.support.v4.widget.TextViewCompat;
 import android.support.v7.appcompat.test.R;
-import android.text.TextUtils;
 import android.text.method.SingleLineTransformationMethod;
 import android.util.AttributeSet;
-import android.util.DisplayMetrics;
-import android.util.TypedValue;
 import android.widget.LinearLayout;
-import android.widget.TextView;
 
-import org.junit.Assert;
-import org.junit.Before;
-import org.junit.Rule;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 
 @MediumTest
 @RunWith(AndroidJUnit4.class)
-public class AppCompatTextViewAutoSizeTest {
-    @Rule
-    public final ActivityTestRule<AppCompatTextViewAutoSizeActivity> mActivityTestRule;
-
-    private Instrumentation mInstrumentation;
-    private AppCompatTextViewAutoSizeActivity mActivity;
+public class AppCompatTextViewAutoSizeTest extends
+        AppCompatBaseAutoSizeTest<AppCompatTextViewAutoSizeActivity, AppCompatTextView> {
 
     public AppCompatTextViewAutoSizeTest() {
-        mActivityTestRule = new ActivityTestRule<>(AppCompatTextViewAutoSizeActivity.class);
+        super(AppCompatTextViewAutoSizeActivity.class);
     }
 
-    @Before
-    public void setup() {
-        mInstrumentation = InstrumentationRegistry.getInstrumentation();
-        mActivity = mActivityTestRule.getActivity();
-    }
-
-    @Test
-    @SdkSuppress(minSdkVersion = 16)
-    // public TextView#getMaxLines only introduced in API 16.
-    public void testAutoSizeCallers_setMaxLines() throws Throwable {
-        final AppCompatTextView autoSizeTextView = prepareAndRetrieveAutoSizeTestData(
-                R.id.textview_autosize_uniform, false);
-        // Configure layout params and auto-size both in pixels to dodge flakiness on different
-        // devices.
-        final LinearLayout.LayoutParams layoutParams = new LinearLayout.LayoutParams(
-                500, 500);
-        final String text = "one two three four five six seven eight nine ten";
-        mActivity.runOnUiThread(new Runnable() {
-            @Override
-            public void run() {
-                autoSizeTextView.setLayoutParams(layoutParams);
-                autoSizeTextView.setAutoSizeTextTypeUniformWithConfiguration(
-                        1 /* autoSizeMinTextSize */,
-                        5000 /* autoSizeMaxTextSize */,
-                        1 /* autoSizeStepGranularity */,
-                        TypedValue.COMPLEX_UNIT_PX);
-                autoSizeTextView.setText(text);
-            }
-        });
-        mInstrumentation.waitForIdleSync();
-
-        float initialSize = 0;
-        for (int i = 1; i < 10; i++) {
-            final int maxLines = i;
-            mActivity.runOnUiThread(new Runnable() {
-                @Override
-                public void run() {
-                    autoSizeTextView.setMaxLines(maxLines);
-                }
-            });
-            mInstrumentation.waitForIdleSync();
-            float expectedSmallerSize = autoSizeTextView.getTextSize();
-            if (i == 1) {
-                initialSize = expectedSmallerSize;
-            }
-
-            mActivity.runOnUiThread(new Runnable() {
-                @Override
-                public void run() {
-                    autoSizeTextView.setMaxLines(maxLines + 1);
-                }
-            });
-            mInstrumentation.waitForIdleSync();
-            assertTrue(expectedSmallerSize <= autoSizeTextView.getTextSize());
-        }
-        assertTrue(initialSize < autoSizeTextView.getTextSize());
-
-        initialSize = 999999;
-        for (int i = 10; i > 1; i--) {
-            final int maxLines = i;
-            mActivity.runOnUiThread(new Runnable() {
-                @Override
-                public void run() {
-                    autoSizeTextView.setMaxLines(maxLines);
-                }
-            });
-            mInstrumentation.waitForIdleSync();
-            float expectedLargerSize = autoSizeTextView.getTextSize();
-            if (i == 10) {
-                initialSize = expectedLargerSize;
-            }
-
-            mActivity.runOnUiThread(new Runnable() {
-                @Override
-                public void run() {
-                    autoSizeTextView.setMaxLines(maxLines - 1);
-                }
-            });
-            mInstrumentation.waitForIdleSync();
-            assertTrue(expectedLargerSize >= autoSizeTextView.getTextSize());
-        }
-        assertTrue(initialSize > autoSizeTextView.getTextSize());
-    }
-
-    @Test
-    public void testAutoSizeCallers_setText() throws Throwable {
-        final AppCompatTextView autoSizeTextView = prepareAndRetrieveAutoSizeTestData(
-                R.id.textview_autosize_uniform, false);
-
-        // Configure layout params and auto-size both in pixels to dodge flakiness on different
-        // devices.
-        final LinearLayout.LayoutParams layoutParams = new LinearLayout.LayoutParams(
-                500, 500);
-        mActivity.runOnUiThread(new Runnable() {
-            @Override
-            public void run() {
-                autoSizeTextView.setLayoutParams(layoutParams);
-                autoSizeTextView.setAutoSizeTextTypeUniformWithConfiguration(
-                        1, 5000, 1, TypedValue.COMPLEX_UNIT_PX);
-            }
-        });
-        mInstrumentation.waitForIdleSync();
-
-        final String initialText = "13characters ";
-        final StringBuilder textToSet = new StringBuilder().append(initialText);
-        float initialSize = 0;
-
-        // As we add characters the text size shrinks.
-        for (int i = 0; i < 10; i++) {
-            mActivity.runOnUiThread(new Runnable() {
-                @Override
-                public void run() {
-                    autoSizeTextView.setText(textToSet.toString());
-                }
-            });
-
-            mInstrumentation.waitForIdleSync();
-            float expectedLargerSize = autoSizeTextView.getTextSize();
-            if (i == 0) {
-                initialSize = expectedLargerSize;
-            }
-
-            textToSet.append(initialText);
-            mActivity.runOnUiThread(new Runnable() {
-                @Override
-                public void run() {
-                    autoSizeTextView.setText(textToSet.toString());
-                }
-            });
-            mInstrumentation.waitForIdleSync();
-            assertTrue(expectedLargerSize >= autoSizeTextView.getTextSize());
-        }
-        assertTrue(initialSize > autoSizeTextView.getTextSize());
-
-        initialSize = 9999999;
-        // As we remove characters the text size expands.
-        for (int i = 9; i >= 0; i--) {
-            mActivity.runOnUiThread(new Runnable() {
-                @Override
-                public void run() {
-                    autoSizeTextView.setText(textToSet.toString());
-                }
-            });
-            mInstrumentation.waitForIdleSync();
-            float expectedSmallerSize = autoSizeTextView.getTextSize();
-            if (i == 0) {
-                initialSize = expectedSmallerSize;
-            }
-
-            textToSet.replace((textToSet.length() - initialText.length()),
-                    textToSet.length(), "");
-            mActivity.runOnUiThread(new Runnable() {
-                @Override
-                public void run() {
-                    autoSizeTextView.setText(textToSet.toString());
-                }
-            });
-            mInstrumentation.waitForIdleSync();
-
-            assertTrue(autoSizeTextView.getTextSize() >= expectedSmallerSize);
-        }
-        assertTrue(autoSizeTextView.getTextSize() > initialSize);
-    }
-
-    @Test
-    public void testAutoSizeUniform_equivalentConfigurations() throws Throwable {
-        final DisplayMetrics dm = mActivity.getResources().getDisplayMetrics();
-        final int minTextSize = 10;
-        final int maxTextSize = 20;
-        final int granularity = 2;
-        final int unit = TypedValue.COMPLEX_UNIT_SP;
-
-        final AppCompatTextView granularityTextView = new AppCompatTextView(mActivity);
-        granularityTextView.setAutoSizeTextTypeUniformWithConfiguration(
-                minTextSize, maxTextSize, granularity, unit);
-
-        final AppCompatTextView presetTextView = new AppCompatTextView(mActivity);
-        presetTextView.setAutoSizeTextTypeUniformWithPresetSizes(
-                new int[]{minTextSize, 12, 14, 16, 18, maxTextSize}, unit);
-
-        // The TextViews have been configured differently but the end result should be nearly
-        // identical.
-        final int expectedAutoSizeType = AppCompatTextView.AUTO_SIZE_TEXT_TYPE_UNIFORM;
-        assertEquals(expectedAutoSizeType, granularityTextView.getAutoSizeTextType());
-        assertEquals(expectedAutoSizeType, presetTextView.getAutoSizeTextType());
-
-        final int expectedMinTextSizeInPx = Math.round(
-                TypedValue.applyDimension(unit, minTextSize, dm));
-        assertEquals(expectedMinTextSizeInPx, granularityTextView.getAutoSizeMinTextSize());
-        assertEquals(expectedMinTextSizeInPx, presetTextView.getAutoSizeMinTextSize());
-
-        final int expectedMaxTextSizeInPx = Math.round(
-                TypedValue.applyDimension(unit, maxTextSize, dm));
-        assertEquals(expectedMaxTextSizeInPx, granularityTextView.getAutoSizeMaxTextSize());
-        assertEquals(expectedMaxTextSizeInPx, presetTextView.getAutoSizeMaxTextSize());
-
-        // Configured with granularity.
-        assertEquals(Math.round(TypedValue.applyDimension(unit, granularity, dm)),
-                granularityTextView.getAutoSizeStepGranularity());
-        // Configured with preset values, there is no granularity.
-        assertEquals(-1, presetTextView.getAutoSizeStepGranularity());
-
-        // Both TextViews generate exactly the same sizes in pixels to choose from when auto-sizing.
-        assertArrayEquals(
-                granularityTextView.getAutoSizeTextAvailableSizes(),
-                presetTextView.getAutoSizeTextAvailableSizes());
-
-        final String someText = "This is a string";
-        final LinearLayout.LayoutParams layoutParams = new LinearLayout.LayoutParams(
-                500, 500);
-        // Configure identically and attach to layout.
-        mActivityTestRule.runOnUiThread(new Runnable() {
-            @Override
-            public void run() {
-                granularityTextView.setLayoutParams(layoutParams);
-                presetTextView.setLayoutParams(layoutParams);
-
-                LinearLayout ll = mActivity.findViewById(R.id.layout_textviewtest);
-                ll.removeAllViews();
-                ll.addView(granularityTextView);
-                ll.addView(presetTextView);
-
-                granularityTextView.setText(someText);
-                presetTextView.setText(someText);
-            }
-        });
-        mInstrumentation.waitForIdleSync();
-
-        assertEquals(granularityTextView.getTextSize(), presetTextView.getTextSize(), 0f);
+    @Override
+    protected AppCompatTextView getNewAutoSizeViewInstance() {
+        return new AppCompatTextView(mActivity);
     }
 
     @Test
     public void testAutoSize_notSupportedByEditText() throws Throwable {
-        final AppCompatEditText autoSizeEditText = (AppCompatEditText) mActivity.findViewById(
+        final AppCompatEditText autoSizeEditText = mActivity.findViewById(
                 R.id.edittext_autosize_uniform);
         // Do not force exact height only.
         final LinearLayout.LayoutParams layoutParams = new LinearLayout.LayoutParams(
@@ -322,854 +75,6 @@
     }
 
     @Test
-    public void testAutoSizeCallers_setHeight() throws Throwable {
-        final AppCompatTextView autoSizeTextView = prepareAndRetrieveAutoSizeTestData(
-                R.id.textview_autosize_uniform, true);
-        // Do not force exact height only.
-        final LinearLayout.LayoutParams layoutParams = new LinearLayout.LayoutParams(
-                200,
-                LinearLayout.LayoutParams.WRAP_CONTENT);
-        mActivityTestRule.runOnUiThread(new Runnable() {
-            @Override
-            public void run() {
-                autoSizeTextView.setLayoutParams(layoutParams);
-            }
-        });
-        mInstrumentation.waitForIdleSync();
-        final float initialTextSize = autoSizeTextView.getTextSize();
-        mActivityTestRule.runOnUiThread(new Runnable() {
-            @Override
-            public void run() {
-                autoSizeTextView.setHeight(autoSizeTextView.getHeight() / 4);
-            }
-        });
-        mInstrumentation.waitForIdleSync();
-
-        assertTrue(autoSizeTextView.getTextSize() < initialTextSize);
-    }
-
-    @Test
-    public void testAutoSizeCallers_setCompoundDrawables() throws Throwable {
-        final AppCompatTextView autoSizeTextView = prepareAndRetrieveAutoSizeTestData(
-                R.id.textview_autosize_uniform, false);
-        final float initialTextSize = autoSizeTextView.getTextSize();
-        final Drawable drawable = ResourcesCompat.getDrawable(mActivity.getResources(),
-                R.drawable.test_drawable_red, null);
-        drawable.setBounds(0, 0, autoSizeTextView.getWidth() / 3, autoSizeTextView.getHeight() / 3);
-        mActivityTestRule.runOnUiThread(new Runnable() {
-            @Override
-            public void run() {
-                autoSizeTextView.setCompoundDrawables(drawable, drawable, drawable, drawable);
-            }
-        });
-        mInstrumentation.waitForIdleSync();
-
-        assertTrue(autoSizeTextView.getTextSize() < initialTextSize);
-    }
-
-    @Test
-    public void testAutoSizeCallers_setCompoundDrawablesRelative() throws Throwable {
-        if (Build.VERSION.SDK_INT >= 17) {
-            final AppCompatTextView autoSizeTextView = prepareAndRetrieveAutoSizeTestData(
-                    R.id.textview_autosize_uniform, false);
-            final float initialTextSize = autoSizeTextView.getTextSize();
-            final Drawable drawable = ResourcesCompat.getDrawable(mActivity.getResources(),
-                    R.drawable.test_drawable_red, null);
-            drawable.setBounds(0, 0, autoSizeTextView.getWidth() / 3,
-                    autoSizeTextView.getHeight() / 3);
-            mActivityTestRule.runOnUiThread(new Runnable() {
-                @Override
-                public void run() {
-                    if (Build.VERSION.SDK_INT >= 17) {
-                        autoSizeTextView.setCompoundDrawablesRelative(
-                                drawable, drawable, drawable, drawable);
-                    }
-                }
-            });
-            mInstrumentation.waitForIdleSync();
-
-            assertTrue(autoSizeTextView.getTextSize() < initialTextSize);
-        }
-    }
-
-    @Test
-    public void testAutoSizeCallers_setCompoundDrawablePadding() throws Throwable {
-        final AppCompatTextView autoSizeTextView = prepareAndRetrieveAutoSizeTestData(
-                R.id.textview_autosize_uniform, false);
-        // Prepare a larger layout in order not to hit the min value easily.
-        mActivityTestRule.runOnUiThread(new Runnable() {
-            @Override
-            public void run() {
-                autoSizeTextView.setWidth(autoSizeTextView.getWidth() * 2);
-                autoSizeTextView.setHeight(autoSizeTextView.getHeight() * 2);
-            }
-        });
-        mInstrumentation.waitForIdleSync();
-        // Setup the drawables before setting their padding in order to modify the available
-        // space and trigger a resize.
-        final Drawable drawable = ResourcesCompat.getDrawable(mActivity.getResources(),
-                R.drawable.test_drawable_red, null);
-        drawable.setBounds(0, 0, autoSizeTextView.getWidth() / 4, autoSizeTextView.getHeight() / 4);
-        mActivityTestRule.runOnUiThread(new Runnable() {
-            @Override
-            public void run() {
-                autoSizeTextView.setCompoundDrawables(
-                        drawable, drawable, drawable, drawable);
-            }
-        });
-        mInstrumentation.waitForIdleSync();
-        final float initialTextSize = autoSizeTextView.getTextSize();
-        mActivityTestRule.runOnUiThread(new Runnable() {
-            @Override
-            public void run() {
-                autoSizeTextView.setCompoundDrawablePadding(
-                        autoSizeTextView.getCompoundDrawablePadding() + 10);
-            }
-        });
-        mInstrumentation.waitForIdleSync();
-
-        assertTrue(autoSizeTextView.getTextSize() < initialTextSize);
-    }
-
-    @Test
-    public void testAutoSizeCallers_setPadding() throws Throwable {
-        final AppCompatTextView autoSizeTextView = prepareAndRetrieveAutoSizeTestData(
-                R.id.textview_autosize_uniform, false);
-        final float initialTextSize = autoSizeTextView.getTextSize();
-        mActivityTestRule.runOnUiThread(new Runnable() {
-            @Override
-            public void run() {
-                autoSizeTextView.setPadding(
-                        autoSizeTextView.getWidth() / 3, autoSizeTextView.getHeight() / 3,
-                        autoSizeTextView.getWidth() / 3, autoSizeTextView.getHeight() / 3);
-            }
-        });
-        mInstrumentation.waitForIdleSync();
-
-        assertTrue(autoSizeTextView.getTextSize() < initialTextSize);
-    }
-
-    @Test
-    public void testAutoSizeCallers_setPaddingRelative() throws Throwable {
-        if (Build.VERSION.SDK_INT >= 16) {
-            final AppCompatTextView autoSizeTextView = prepareAndRetrieveAutoSizeTestData(
-                    R.id.textview_autosize_uniform, false);
-            final float initialTextSize = autoSizeTextView.getTextSize();
-
-            mActivityTestRule.runOnUiThread(new Runnable() {
-                @Override
-                public void run() {
-                    if (Build.VERSION.SDK_INT > 16) {
-                        autoSizeTextView.setPaddingRelative(
-                                autoSizeTextView.getWidth() / 3, autoSizeTextView.getHeight() / 3,
-                                autoSizeTextView.getWidth() / 3, autoSizeTextView.getHeight() / 3);
-                    }
-                }
-            });
-            mInstrumentation.waitForIdleSync();
-
-            assertTrue(autoSizeTextView.getTextSize() < initialTextSize);
-        }
-    }
-
-    @Test
-    public void testAutoSizeCallers_setTypeface() throws Throwable {
-        final AppCompatTextView autoSizeTextView = prepareAndRetrieveAutoSizeTestData(
-                R.id.textview_autosize_uniform, false);
-        mActivityTestRule.runOnUiThread(new Runnable() {
-            @Override
-            public void run() {
-                autoSizeTextView.setText("The typeface change needs a bit more text then "
-                        + "the default used for this batch of tests in order to get to resize text."
-                        + " The resize function is always called but even with different typefaces "
-                        + "there may not be a need to resize text because it just fits. The longer "
-                        + "the text, the higher the chance for a resize. And here is yet another "
-                        + "sentence to make sure this test is not flaky. Not flaky at all.");
-            }
-        });
-        mInstrumentation.waitForIdleSync();
-        final float initialTextSize = autoSizeTextView.getTextSize();
-
-        mActivityTestRule.runOnUiThread(new Runnable() {
-            @Override
-            public void run() {
-                Typeface differentTypeface = Typeface.MONOSPACE;
-                if (autoSizeTextView.getTypeface() == Typeface.MONOSPACE) {
-                    differentTypeface = Typeface.SANS_SERIF;
-                }
-                autoSizeTextView.setTypeface(differentTypeface);
-            }
-        });
-        mInstrumentation.waitForIdleSync();
-        final float changedTextSize = autoSizeTextView.getTextSize();
-
-        // Don't really know if it is larger or smaller (depends on the typeface chosen above),
-        // but it should definitely have changed.
-        assertNotEquals(initialTextSize, changedTextSize, 0f);
-
-        mActivityTestRule.runOnUiThread(new Runnable() {
-            @Override
-            public void run() {
-                autoSizeTextView.setTypeface(autoSizeTextView.getTypeface());
-            }
-        });
-        mInstrumentation.waitForIdleSync();
-
-        assertEquals(changedTextSize, autoSizeTextView.getTextSize(), 0f);
-    }
-
-    @Test
-    public void testAutoSizeCallers_setLetterSpacing() throws Throwable {
-        if (Build.VERSION.SDK_INT >= 21) {
-            final AppCompatTextView autoSizeTextView = prepareAndRetrieveAutoSizeTestData(
-                    R.id.textview_autosize_uniform, false);
-            final float initialTextSize = autoSizeTextView.getTextSize();
-
-            mActivityTestRule.runOnUiThread(new Runnable() {
-                @Override
-                public void run() {
-                    if (Build.VERSION.SDK_INT >= 21) {
-                        autoSizeTextView.setLetterSpacing(
-                                autoSizeTextView.getLetterSpacing() * 1.5f + 4.5f);
-                    }
-                }
-            });
-            mInstrumentation.waitForIdleSync();
-            final float changedTextSize = autoSizeTextView.getTextSize();
-
-            assertTrue(changedTextSize < initialTextSize);
-
-            mActivityTestRule.runOnUiThread(new Runnable() {
-                @Override
-                public void run() {
-                    if (Build.VERSION.SDK_INT >= 21) {
-                        autoSizeTextView.setLetterSpacing(autoSizeTextView.getLetterSpacing());
-                    }
-                }
-            });
-            mInstrumentation.waitForIdleSync();
-
-            assertEquals(changedTextSize, autoSizeTextView.getTextSize(), 0f);
-        }
-    }
-
-    @Test
-    public void testAutoSizeCallers_setMaxHeight() throws Throwable {
-        final AppCompatTextView autoSizeTextView = prepareAndRetrieveAutoSizeTestData(
-                R.id.textview_autosize_uniform, true);
-        // Do not force exact height only.
-        final LinearLayout.LayoutParams layoutParams = new LinearLayout.LayoutParams(
-                200,
-                LinearLayout.LayoutParams.WRAP_CONTENT);
-        mActivityTestRule.runOnUiThread(new Runnable() {
-            @Override
-            public void run() {
-                autoSizeTextView.setLayoutParams(layoutParams);
-            }
-        });
-        mInstrumentation.waitForIdleSync();
-        final float initialTextSize = autoSizeTextView.getTextSize();
-        mActivityTestRule.runOnUiThread(new Runnable() {
-            @Override
-            public void run() {
-                autoSizeTextView.setMaxHeight(
-                        autoSizeTextView.getHeight() / 4);
-            }
-        });
-        mInstrumentation.waitForIdleSync();
-
-        assertTrue(autoSizeTextView.getTextSize() < initialTextSize);
-    }
-
-    @Test
-    public void testAutoSizeCallers_setMaxWidth() throws Throwable {
-        final AppCompatTextView autoSizeTextView = prepareAndRetrieveAutoSizeTestData(
-                R.id.textview_autosize_uniform, true);
-        // Do not force exact width only.
-        final LinearLayout.LayoutParams layoutParams = new LinearLayout.LayoutParams(
-                LinearLayout.LayoutParams.WRAP_CONTENT,
-                200);
-        mActivityTestRule.runOnUiThread(new Runnable() {
-            @Override
-            public void run() {
-                autoSizeTextView.setLayoutParams(layoutParams);
-            }
-        });
-        mInstrumentation.waitForIdleSync();
-        final float initialTextSize = autoSizeTextView.getTextSize();
-        mActivityTestRule.runOnUiThread(new Runnable() {
-            @Override
-            public void run() {
-                autoSizeTextView.setMaxWidth(
-                        autoSizeTextView.getWidth() / 4);
-            }
-        });
-        mInstrumentation.waitForIdleSync();
-
-        assertTrue(autoSizeTextView.getTextSize() != initialTextSize);
-    }
-
-    @Test
-    public void testAutoSizeCallers_setWidth() throws Throwable {
-        final AppCompatTextView autoSizeTextView = prepareAndRetrieveAutoSizeTestData(
-                R.id.textview_autosize_uniform, true);
-        // Do not force exact width only.
-        final LinearLayout.LayoutParams layoutParams = new LinearLayout.LayoutParams(
-                LinearLayout.LayoutParams.WRAP_CONTENT,
-                200);
-        mActivityTestRule.runOnUiThread(new Runnable() {
-            @Override
-            public void run() {
-                autoSizeTextView.setLayoutParams(layoutParams);
-            }
-        });
-        mInstrumentation.waitForIdleSync();
-
-        final float initialTextSize = autoSizeTextView.getTextSize();
-        mActivityTestRule.runOnUiThread(new Runnable() {
-            @Override
-            public void run() {
-                autoSizeTextView.setWidth(
-                        autoSizeTextView.getWidth() / 4);
-            }
-        });
-        mInstrumentation.waitForIdleSync();
-
-        assertTrue(autoSizeTextView.getTextSize() != initialTextSize);
-    }
-
-    @Test
-    public void testAutoSizeCallers_setTextSizeIsNoOp() throws Throwable {
-        final AppCompatTextView autoSizeTextView = prepareAndRetrieveAutoSizeTestData(
-                R.id.textview_autosize_uniform, false);
-        final float initialTextSize = autoSizeTextView.getTextSize();
-
-        mActivityTestRule.runOnUiThread(new Runnable() {
-            @Override
-            public void run() {
-                autoSizeTextView.setTextSize(initialTextSize + 123f);
-            }
-        });
-        mInstrumentation.waitForIdleSync();
-
-        assertEquals(initialTextSize, autoSizeTextView.getTextSize(), 0f);
-    }
-
-    @Test
-    public void testAutoSizeCallers_setHorizontallyScrolling() throws Throwable {
-        final AppCompatTextView autoSizeTextView = prepareAndRetrieveAutoSizeTestData(
-                R.id.textview_autosize_uniform, false);
-        // Horizontal scrolling is expected to be deactivated for this test.
-        final float initialTextSize = autoSizeTextView.getTextSize();
-        mActivityTestRule.runOnUiThread(new Runnable() {
-            @Override
-            public void run() {
-                autoSizeTextView.setHorizontallyScrolling(true);
-            }
-        });
-        mInstrumentation.waitForIdleSync();
-        assertTrue(autoSizeTextView.getTextSize() > initialTextSize);
-
-        mActivityTestRule.runOnUiThread(new Runnable() {
-            @Override
-            public void run() {
-                autoSizeTextView.setHorizontallyScrolling(false);
-            }
-        });
-        mInstrumentation.waitForIdleSync();
-        Assert.assertEquals(initialTextSize, autoSizeTextView.getTextSize(), 0f);
-    }
-
-    @Test
-    public void testAutoSize_setEllipsize() throws Throwable {
-        final AppCompatTextView textView = (AppCompatTextView) mActivity.findViewById(
-                R.id.textview_autosize_uniform_predef_sizes);
-        final int initialAutoSizeType = textView.getAutoSizeTextType();
-        final int initialMinTextSize = textView.getAutoSizeMinTextSize();
-        final int initialMaxTextSize = textView.getAutoSizeMaxTextSize();
-        final int initialAutoSizeGranularity = textView.getAutoSizeStepGranularity();
-        final int initialSizes = textView.getAutoSizeTextAvailableSizes().length;
-
-        Assert.assertEquals(null, textView.getEllipsize());
-        // Verify styled attributes.
-        Assert.assertEquals(TextViewCompat.AUTO_SIZE_TEXT_TYPE_UNIFORM, initialAutoSizeType);
-        assertNotEquals(-1, initialMinTextSize);
-        assertNotEquals(-1, initialMaxTextSize);
-        // Because this TextView has been configured to use predefined sizes.
-        Assert.assertEquals(-1, initialAutoSizeGranularity);
-        assertNotEquals(0, initialSizes);
-
-        final TextUtils.TruncateAt newEllipsizeValue = TextUtils.TruncateAt.END;
-        mActivityTestRule.runOnUiThread(new Runnable() {
-            @Override
-            public void run() {
-                textView.setEllipsize(newEllipsizeValue);
-            }
-        });
-        mInstrumentation.waitForIdleSync();
-        Assert.assertEquals(newEllipsizeValue, textView.getEllipsize());
-        // Beside the ellipsis no auto-size attribute has changed.
-        Assert.assertEquals(initialAutoSizeType, textView.getAutoSizeTextType());
-        Assert.assertEquals(initialMinTextSize, textView.getAutoSizeMinTextSize());
-        Assert.assertEquals(initialMaxTextSize, textView.getAutoSizeMaxTextSize());
-        Assert.assertEquals(initialAutoSizeGranularity, textView.getAutoSizeStepGranularity());
-        Assert.assertEquals(initialSizes, textView.getAutoSizeTextAvailableSizes().length);
-    }
-
-    @Test
-    public void testEllipsize_setAutoSize() throws Throwable {
-        final AppCompatTextView textView =
-                (AppCompatTextView) mActivity.findViewById(R.id.textview_text);
-        final TextUtils.TruncateAt newEllipsizeValue = TextUtils.TruncateAt.END;
-        mActivityTestRule.runOnUiThread(new Runnable() {
-            @Override
-            public void run() {
-                textView.setEllipsize(newEllipsizeValue);
-            }
-        });
-        mInstrumentation.waitForIdleSync();
-        Assert.assertEquals(newEllipsizeValue, textView.getEllipsize());
-        Assert.assertEquals(TextViewCompat.AUTO_SIZE_TEXT_TYPE_NONE,
-                textView.getAutoSizeTextType());
-        Assert.assertEquals(-1, textView.getAutoSizeMinTextSize());
-        Assert.assertEquals(-1, textView.getAutoSizeMaxTextSize());
-        Assert.assertEquals(-1, textView.getAutoSizeStepGranularity());
-        Assert.assertEquals(0, textView.getAutoSizeTextAvailableSizes().length);
-
-        mActivityTestRule.runOnUiThread(new Runnable() {
-            @Override
-            public void run() {
-                textView.setAutoSizeTextTypeWithDefaults(
-                        TextViewCompat.AUTO_SIZE_TEXT_TYPE_UNIFORM);
-            }
-        });
-        mInstrumentation.waitForIdleSync();
-        Assert.assertEquals(newEllipsizeValue, textView.getEllipsize());
-        Assert.assertEquals(TextViewCompat.AUTO_SIZE_TEXT_TYPE_UNIFORM,
-                textView.getAutoSizeTextType());
-        // The auto-size defaults have been used.
-        assertNotEquals(-1, textView.getAutoSizeMinTextSize());
-        assertNotEquals(-1, textView.getAutoSizeMaxTextSize());
-        assertNotEquals(-1, textView.getAutoSizeStepGranularity());
-        assertNotEquals(0, textView.getAutoSizeTextAvailableSizes().length);
-    }
-
-    @Test
-    public void testAutoSizeUniform_obtainStyledAttributesUsingPredefinedSizes() {
-        DisplayMetrics m = mActivity.getResources().getDisplayMetrics();
-        final AppCompatTextView autoSizeTextViewUniform =
-                (AppCompatTextView) mActivity.findViewById(
-                        R.id.textview_autosize_uniform_predef_sizes);
-
-        // In arrays.xml predefined the step sizes as: 5px, 11dip, 19sp, 29pt, 43mm and 53in.
-        // TypedValue can not use the math library and instead rounds the value by adding
-        // 0.5f when obtaining styled attributes. Check TypedValue#complexToDimensionPixelSize(...)
-        int[] expectedSizesInPx = new int[] {
-                (int) (0.5f + TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_PX, 5f, m)),
-                (int) (0.5f + TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 11f, m)),
-                (int) (0.5f + TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_SP, 19f, m)),
-                (int) (0.5f + TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_PT, 29f, m)),
-                (int) (0.5f + TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_MM, 43f, m)),
-                (int) (0.5f + TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_IN, 53f, m))};
-
-        boolean containsValueFromExpectedSizes = false;
-        final int textSize = (int) autoSizeTextViewUniform.getTextSize();
-        for (int i = 0; i < expectedSizesInPx.length; i++) {
-            if (expectedSizesInPx[i] == textSize) {
-                containsValueFromExpectedSizes = true;
-                break;
-            }
-        }
-        assertTrue(containsValueFromExpectedSizes);
-    }
-
-    @Test
-    public void testAutoSizeUniform_obtainStyledAttributesPredefinedSizesFiltering() {
-        AppCompatTextView autoSizeTextViewUniform = (AppCompatTextView) mActivity.findViewById(
-                R.id.textview_autosize_uniform_predef_sizes_redundant_values);
-
-        // In arrays.xml predefined the step sizes as: 40px, 10px, 10px, 10px, 0dp.
-        final int[] expectedSizes = new int[] {10, 40};
-        assertArrayEquals(expectedSizes, autoSizeTextViewUniform.getAutoSizeTextAvailableSizes());
-    }
-
-    @Test
-    public void testAutoSizeUniform_predefinedSizesFilteringAndSorting() throws Throwable {
-        final AppCompatTextView textView = (AppCompatTextView) mActivity.findViewById(
-                R.id.textview_text);
-        Assert.assertEquals(TextViewCompat.AUTO_SIZE_TEXT_TYPE_NONE,
-                textView.getAutoSizeTextType());
-
-        final int[] predefinedSizes = new int[] {400, 0, 10, 40, 10, 10, 0, 0};
-        mActivityTestRule.runOnUiThread(new Runnable() {
-            @Override
-            public void run() {
-                textView.setAutoSizeTextTypeUniformWithPresetSizes(
-                        predefinedSizes, TypedValue.COMPLEX_UNIT_PX);
-            }
-        });
-        mInstrumentation.waitForIdleSync();
-        assertArrayEquals(new int[] {10, 40, 400}, textView.getAutoSizeTextAvailableSizes());
-    }
-
-    @Test(expected = NullPointerException.class)
-    public void testAutoSizeUniform_predefinedSizesNullArray() throws Throwable {
-        final AppCompatTextView textView = (AppCompatTextView) mActivity.findViewById(
-                R.id.textview_text);
-        Assert.assertEquals(TextViewCompat.AUTO_SIZE_TEXT_TYPE_NONE,
-                textView.getAutoSizeTextType());
-
-        final int[] predefinedSizes = null;
-        mActivityTestRule.runOnUiThread(new Runnable() {
-            @Override
-            public void run() {
-                textView.setAutoSizeTextTypeUniformWithPresetSizes(
-                        predefinedSizes, TypedValue.COMPLEX_UNIT_PX);
-            }
-        });
-        mInstrumentation.waitForIdleSync();
-    }
-
-    @Test
-    public void testAutoSizeUniform_predefinedSizesEmptyArray() throws Throwable {
-        final AppCompatTextView textView = (AppCompatTextView) mActivity.findViewById(
-                R.id.textview_text);
-        Assert.assertEquals(TextViewCompat.AUTO_SIZE_TEXT_TYPE_NONE,
-                textView.getAutoSizeTextType());
-
-        mActivityTestRule.runOnUiThread(new Runnable() {
-            @Override
-            public void run() {
-                textView.setAutoSizeTextTypeWithDefaults(
-                        TextViewCompat.AUTO_SIZE_TEXT_TYPE_UNIFORM);
-            }
-        });
-        mInstrumentation.waitForIdleSync();
-
-        final int[] defaultSizes = textView.getAutoSizeTextAvailableSizes();
-        assertNotNull(defaultSizes);
-        assertTrue(defaultSizes.length > 0);
-
-        final int[] predefinedSizes = new int[0];
-        mActivityTestRule.runOnUiThread(new Runnable() {
-            @Override
-            public void run() {
-                textView.setAutoSizeTextTypeUniformWithPresetSizes(
-                        predefinedSizes, TypedValue.COMPLEX_UNIT_PX);
-            }
-        });
-        mInstrumentation.waitForIdleSync();
-
-        final int[] newSizes = textView.getAutoSizeTextAvailableSizes();
-        assertNotNull(defaultSizes);
-        assertArrayEquals(defaultSizes, newSizes);
-    }
-
-    @Test
-    public void testAutoSizeUniform_buildsSizes() throws Throwable {
-        final AppCompatTextView autoSizeTextViewUniform =
-                (AppCompatTextView) mActivity.findViewById(
-                        R.id.textview_autosize_uniform);
-
-        // Verify that the interval limits are both included.
-        mActivityTestRule.runOnUiThread(new Runnable() {
-            @Override
-            public void run() {
-                autoSizeTextViewUniform
-                        .setAutoSizeTextTypeUniformWithConfiguration(10, 20, 2,
-                                TypedValue.COMPLEX_UNIT_PX);
-            }
-        });
-        mInstrumentation.waitForIdleSync();
-        assertArrayEquals(
-                new int[] {10, 12, 14, 16, 18, 20},
-                autoSizeTextViewUniform.getAutoSizeTextAvailableSizes());
-
-        mActivityTestRule.runOnUiThread(new Runnable() {
-            @Override
-            public void run() {
-                autoSizeTextViewUniform
-                        .setAutoSizeTextTypeUniformWithConfiguration(
-                                autoSizeTextViewUniform.getAutoSizeMinTextSize(),
-                                19,
-                                autoSizeTextViewUniform.getAutoSizeStepGranularity(),
-                                TypedValue.COMPLEX_UNIT_PX);
-            }
-        });
-        mInstrumentation.waitForIdleSync();
-        assertArrayEquals(
-                new int[] {10, 12, 14, 16, 18},
-                autoSizeTextViewUniform.getAutoSizeTextAvailableSizes());
-
-        mActivityTestRule.runOnUiThread(new Runnable() {
-            @Override
-            public void run() {
-                autoSizeTextViewUniform
-                        .setAutoSizeTextTypeUniformWithConfiguration(
-                                autoSizeTextViewUniform.getAutoSizeMinTextSize(),
-                                21,
-                                autoSizeTextViewUniform.getAutoSizeStepGranularity(),
-                                TypedValue.COMPLEX_UNIT_PX);
-            }
-        });
-        mInstrumentation.waitForIdleSync();
-        assertArrayEquals(
-                new int[] {10, 12, 14, 16, 18, 20},
-                autoSizeTextViewUniform.getAutoSizeTextAvailableSizes());
-    }
-
-    @Test
-    public void testAutoSizeUniform_getSetAutoSizeTextDefaults() {
-        final AppCompatTextView textView = new AppCompatTextView(mActivity);
-        Assert.assertEquals(TextViewCompat.AUTO_SIZE_TEXT_TYPE_NONE,
-                textView.getAutoSizeTextType());
-        // Min/Max/Granularity values for auto-sizing are 0 because they are not used.
-        Assert.assertEquals(-1, textView.getAutoSizeMinTextSize());
-        Assert.assertEquals(-1, textView.getAutoSizeMaxTextSize());
-        Assert.assertEquals(-1, textView.getAutoSizeStepGranularity());
-
-        textView.setAutoSizeTextTypeWithDefaults(TextViewCompat.AUTO_SIZE_TEXT_TYPE_UNIFORM);
-        Assert.assertEquals(TextViewCompat.AUTO_SIZE_TEXT_TYPE_UNIFORM,
-                textView.getAutoSizeTextType());
-        // Min/Max default values for auto-sizing XY have been loaded.
-        final int minSize = textView.getAutoSizeMinTextSize();
-        final int maxSize = textView.getAutoSizeMaxTextSize();
-        assertTrue(0 < minSize);
-        assertTrue(minSize < maxSize);
-        assertNotEquals(0, textView.getAutoSizeStepGranularity());
-
-        textView.setAutoSizeTextTypeWithDefaults(TextViewCompat.AUTO_SIZE_TEXT_TYPE_NONE);
-        Assert.assertEquals(TextViewCompat.AUTO_SIZE_TEXT_TYPE_NONE,
-                textView.getAutoSizeTextType());
-        // Min/Max values for auto-sizing XY have been cleared.
-        Assert.assertEquals(-1, textView.getAutoSizeMinTextSize());
-        Assert.assertEquals(-1, textView.getAutoSizeMaxTextSize());
-        Assert.assertEquals(-1, textView.getAutoSizeStepGranularity());
-    }
-
-    @Test
-    public void testAutoSizeUniform_getSetAutoSizeStepGranularity() {
-        final AppCompatTextView textView = new AppCompatTextView(mActivity);
-        Assert.assertEquals(TextViewCompat.AUTO_SIZE_TEXT_TYPE_NONE,
-                textView.getAutoSizeTextType());
-        final int initialValue = -1;
-        Assert.assertEquals(initialValue, textView.getAutoSizeStepGranularity());
-
-        textView.setAutoSizeTextTypeWithDefaults(TextViewCompat.AUTO_SIZE_TEXT_TYPE_UNIFORM);
-        Assert.assertEquals(TextViewCompat.AUTO_SIZE_TEXT_TYPE_UNIFORM,
-                textView.getAutoSizeTextType());
-        final int defaultValue = 1; // 1px.
-        // If the auto-size type is AUTO_SIZE_TEXT_TYPE_UNIFORM then it means textView went through
-        // the auto-size setup and given that 0 is an invalid value it changed it to the default.
-        Assert.assertEquals(defaultValue, textView.getAutoSizeStepGranularity());
-
-        final int newValue = 33;
-        textView.setAutoSizeTextTypeUniformWithConfiguration(
-                textView.getAutoSizeMinTextSize(),
-                textView.getAutoSizeMaxTextSize(),
-                newValue,
-                TypedValue.COMPLEX_UNIT_PX);
-        Assert.assertEquals(newValue, textView.getAutoSizeStepGranularity());
-    }
-
-    @Test
-    public void testAutoSizeUniform_getSetAutoSizeMinTextSize() {
-        final AppCompatTextView textView = new AppCompatTextView(mActivity);
-        textView.setAutoSizeTextTypeWithDefaults(TextViewCompat.AUTO_SIZE_TEXT_TYPE_UNIFORM);
-        Assert.assertEquals(TextViewCompat.AUTO_SIZE_TEXT_TYPE_UNIFORM,
-                textView.getAutoSizeTextType());
-        final int minSize = textView.getAutoSizeMinTextSize();
-        assertNotEquals(0, minSize);
-        final int maxSize = textView.getAutoSizeMaxTextSize();
-        assertNotEquals(0, maxSize);
-
-        // This is just a test check to verify the next assertions. If this fails it is a problem
-        // of this test setup (we need at least 2 units).
-        assertTrue((maxSize - minSize) > 1);
-        final int newMinSize = maxSize - 1;
-        textView.setAutoSizeTextTypeUniformWithConfiguration(
-                newMinSize,
-                textView.getAutoSizeMaxTextSize(),
-                textView.getAutoSizeStepGranularity(),
-                TypedValue.COMPLEX_UNIT_PX);
-
-        Assert.assertEquals(newMinSize, textView.getAutoSizeMinTextSize());
-        // Max size has not changed.
-        Assert.assertEquals(maxSize, textView.getAutoSizeMaxTextSize());
-
-        textView.setAutoSizeTextTypeUniformWithConfiguration(
-                newMinSize,
-                newMinSize + 10,
-                textView.getAutoSizeStepGranularity(),
-                TypedValue.COMPLEX_UNIT_SP);
-
-        // It does not matter which unit has been used to set the min size, the getter always
-        // returns it in pixels.
-        Assert.assertEquals(Math.round(TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_SP,
-                newMinSize, mActivity.getResources().getDisplayMetrics())),
-                        textView.getAutoSizeMinTextSize());
-    }
-
-    @Test(expected = IllegalArgumentException.class)
-    public void testAutoSizeUniform_throwsException_whenMaxLessThanMin() {
-        final AppCompatTextView textView = new AppCompatTextView(mActivity);
-        textView.setAutoSizeTextTypeUniformWithConfiguration(
-                10, 9, 1, TypedValue.COMPLEX_UNIT_SP);
-    }
-
-    @Test(expected = IllegalArgumentException.class)
-    public void testAutoSizeUniform_throwsException_minLessThanZero() {
-        final AppCompatTextView textView = new AppCompatTextView(mActivity);
-        textView.setAutoSizeTextTypeUniformWithConfiguration(
-                -1, 9, 1, TypedValue.COMPLEX_UNIT_SP);
-    }
-
-    @Test(expected = IllegalArgumentException.class)
-    public void testAutoSizeUniform_throwsException_maxLessThanZero() {
-        final AppCompatTextView textView = new AppCompatTextView(mActivity);
-        textView.setAutoSizeTextTypeUniformWithConfiguration(
-                10, -1, 1, TypedValue.COMPLEX_UNIT_SP);
-    }
-
-    @Test(expected = IllegalArgumentException.class)
-    public void testAutoSizeUniform_throwsException_granularityLessThanZero() {
-        final AppCompatTextView textView = new AppCompatTextView(mActivity);
-        textView.setAutoSizeTextTypeUniformWithConfiguration(
-                10, 20, -1, TypedValue.COMPLEX_UNIT_SP);
-    }
-
-    @Test
-    public void testAutoSizeUniform_getSetAutoSizeMaxTextSize() {
-        final AppCompatTextView textView = new AppCompatTextView(mActivity);
-        textView.setAutoSizeTextTypeWithDefaults(TextViewCompat.AUTO_SIZE_TEXT_TYPE_UNIFORM);
-        Assert.assertEquals(TextViewCompat.AUTO_SIZE_TEXT_TYPE_UNIFORM,
-                textView.getAutoSizeTextType());
-        final int minSize = textView.getAutoSizeMinTextSize();
-        assertNotEquals(0, minSize);
-        final int maxSize = textView.getAutoSizeMaxTextSize();
-        assertNotEquals(0, maxSize);
-
-        final int newMaxSize = maxSize + 11;
-        textView.setAutoSizeTextTypeUniformWithConfiguration(
-                textView.getAutoSizeMinTextSize(),
-                newMaxSize,
-                textView.getAutoSizeStepGranularity(),
-                TypedValue.COMPLEX_UNIT_PX);
-
-        Assert.assertEquals(newMaxSize, textView.getAutoSizeMaxTextSize());
-        // Min size has not changed.
-        Assert.assertEquals(minSize, textView.getAutoSizeMinTextSize());
-        textView.setAutoSizeTextTypeUniformWithConfiguration(
-                textView.getAutoSizeMinTextSize(),
-                newMaxSize,
-                textView.getAutoSizeStepGranularity(),
-                TypedValue.COMPLEX_UNIT_SP);
-        // It does not matter which unit has been used to set the max size, the getter always
-        // returns it in pixels.
-        Assert.assertEquals(Math.round(TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_SP,
-                newMaxSize, mActivity.getResources().getDisplayMetrics())),
-                        textView.getAutoSizeMaxTextSize());
-    }
-
-    @Test
-    public void testAutoSizeUniform_autoSizeCalledWhenTypeChanged() throws Throwable {
-        final AppCompatTextView textView = (AppCompatTextView) mActivity.findViewById(
-                R.id.textview_text);
-        // Make sure we pick an already inflated non auto-sized text view.
-        Assert.assertEquals(TextViewCompat.AUTO_SIZE_TEXT_TYPE_NONE,
-                textView.getAutoSizeTextType());
-        // Set the text size to a very low value in order to prepare for auto-size.
-        final int customTextSize = 3;
-        mActivityTestRule.runOnUiThread(new Runnable() {
-            @Override
-            public void run() {
-                textView.setTextSize(TypedValue.COMPLEX_UNIT_PX, customTextSize);
-            }
-        });
-        mInstrumentation.waitForIdleSync();
-        Assert.assertEquals(customTextSize, textView.getTextSize(), 0f);
-        mActivityTestRule.runOnUiThread(new Runnable() {
-            @Override
-            public void run() {
-                textView.setAutoSizeTextTypeWithDefaults(
-                        TextViewCompat.AUTO_SIZE_TEXT_TYPE_UNIFORM);
-            }
-        });
-        mInstrumentation.waitForIdleSync();
-        // The size of the text should have changed.
-        assertNotEquals(customTextSize, textView.getTextSize(), 0f);
-    }
-
-    @Test
-    public void testAutoSizeCallers_setTextSizeChangesSizeWhenAutoSizeDisabled() throws Throwable {
-        final AppCompatTextView textView = (AppCompatTextView) prepareAndRetrieveAutoSizeTestData(
-                R.id.textview_autosize_uniform, false);
-        // Disable auto-size.
-        mActivityTestRule.runOnUiThread(new Runnable() {
-            @Override
-            public void run() {
-                textView.setAutoSizeTextTypeWithDefaults(TextViewCompat.AUTO_SIZE_TEXT_TYPE_NONE);
-            }
-        });
-        mInstrumentation.waitForIdleSync();
-        final float newTextSizeInPx = 123f;
-        assertNotEquals(newTextSizeInPx, textView.getTextSize(), 0f);
-
-        mActivityTestRule.runOnUiThread(new Runnable() {
-            @Override
-            public void run() {
-                textView.setTextSize(TypedValue.COMPLEX_UNIT_PX, newTextSizeInPx);
-            }
-        });
-        mInstrumentation.waitForIdleSync();
-
-        assertEquals(newTextSizeInPx, textView.getTextSize(), 0f);
-    }
-
-    /**
-     * Some TextView attributes require non-fixed width and/or layout height. This function removes
-     * all other existing views from the layout leaving only one auto-size TextView (for exercising
-     * the auto-size behavior) which has been set up to suit the test needs.
-     *
-     * @param viewId The id of the view to prepare.
-     * @param shouldWrapLayoutContent Specifies if the layout params should wrap content
-     *
-     * @return a TextView configured for auto size tests.
-     */
-    private AppCompatTextView prepareAndRetrieveAutoSizeTestData(final int viewId,
-            final boolean shouldWrapLayoutContent) throws Throwable {
-        mActivityTestRule.runOnUiThread(new Runnable() {
-            @Override
-            public void run() {
-                LinearLayout ll = (LinearLayout) mActivity.findViewById(
-                        android.support.v7.appcompat.test.R.id.layout_textviewtest);
-                AppCompatTextView targetedTextView =
-                        (AppCompatTextView) mActivity.findViewById(viewId);
-                ll.removeAllViews();
-                ll.addView(targetedTextView);
-            }
-        });
-        mInstrumentation.waitForIdleSync();
-
-        final AppCompatTextView textView = mActivity.findViewById(viewId);
-        if (shouldWrapLayoutContent) {
-            // Do not force exact width or height.
-            final LinearLayout.LayoutParams layoutParams = new LinearLayout.LayoutParams(
-                    LinearLayout.LayoutParams.WRAP_CONTENT,
-                    LinearLayout.LayoutParams.WRAP_CONTENT);
-            mActivityTestRule.runOnUiThread(new Runnable() {
-                @Override
-                public void run() {
-                    textView.setLayoutParams(layoutParams);
-                }
-            });
-            mInstrumentation.waitForIdleSync();
-        }
-
-        return textView;
-    }
-
-    @Test
     public void testAutoSizeWithMaxLines_shouldNotThrowException() throws Throwable {
         // the layout contains an instance of CustomTextViewWithTransformationMethod
         final AppCompatTextView textView = (AppCompatTextView) mActivity
@@ -1179,7 +84,7 @@
         if (Build.VERSION.SDK_INT >= 16) {
             assertEquals(1, textView.getMaxLines());
         }
-        assertEquals(TextView.AUTO_SIZE_TEXT_TYPE_UNIFORM, textView.getAutoSizeTextType());
+        assertEquals(TextViewCompat.AUTO_SIZE_TEXT_TYPE_UNIFORM, textView.getAutoSizeTextType());
         assertTrue(textView.getTransformationMethod() instanceof SingleLineTransformationMethod);
     }
 
diff --git a/v7/appcompat/tests/src/android/support/v7/widget/ListPopupWindowTest.java b/v7/appcompat/tests/src/android/support/v7/widget/ListPopupWindowTest.java
index 7ef6a85..2681d0f 100644
--- a/v7/appcompat/tests/src/android/support/v7/widget/ListPopupWindowTest.java
+++ b/v7/appcompat/tests/src/android/support/v7/widget/ListPopupWindowTest.java
@@ -182,9 +182,14 @@
         Builder popupBuilder = new Builder().setModal(setupAsModal).withDismissListener();
         popupBuilder.wireToActionButton();
 
+        final View.OnClickListener mockContainerClickListener = mock(View.OnClickListener.class);
         // Also register a click listener on the top-level container
-        View.OnClickListener mockContainerClickListener = mock(View.OnClickListener.class);
-        mContainer.setOnClickListener(mockContainerClickListener);
+        mActivityTestRule.runOnUiThread(new Runnable() {
+            @Override
+            public void run() {
+                mContainer.setOnClickListener(mockContainerClickListener);
+            }
+        });
 
         onView(withId(R.id.test_button)).perform(click());
         assertTrue("Popup window showing", mListPopupWindow.isShowing());
diff --git a/v7/cardview/lint-baseline.xml b/v7/cardview/lint-baseline.xml
index 172bbf6..fb44511 100644
--- a/v7/cardview/lint-baseline.xml
+++ b/v7/cardview/lint-baseline.xml
@@ -1,4 +1,4 @@
 <?xml version="1.0" encoding="UTF-8"?>
-<issues format="4" by="lint 2.4.0-alpha6">
+<issues format="4" by="lint 3.0.0-alpha5">
 
 </issues>
diff --git a/v7/gridlayout/lint-baseline.xml b/v7/gridlayout/lint-baseline.xml
index 1c675b0..b373b5e 100644
--- a/v7/gridlayout/lint-baseline.xml
+++ b/v7/gridlayout/lint-baseline.xml
@@ -1,103 +1,15 @@
 <?xml version="1.0" encoding="UTF-8"?>
-<issues format="4" by="lint 2.4.0-alpha6">
-
-    <issue
-        id="Range"
-        message="Value must be ≥ 0 (was -1)"
-        errorLine1="                childHeightSpec = MeasureSpec.makeMeasureSpec(maxLayoutHeight, MeasureSpec.AT_MOST);"
-        errorLine2="                                                              ~~~~~~~~~~~~~~~">
-        <location
-            file="java/android/support/v4/widget/SlidingPaneLayout.java"
-            line="536"
-            column="63"/>
-    </issue>
-
-    <issue
-        id="Range"
-        message="Value must be ≥ 0 (was -1)"
-        errorLine1="                childHeightSpec = MeasureSpec.makeMeasureSpec(maxLayoutHeight, MeasureSpec.EXACTLY);"
-        errorLine2="                                                              ~~~~~~~~~~~~~~~">
-        <location
-            file="java/android/support/v4/widget/SlidingPaneLayout.java"
-            line="538"
-            column="63"/>
-    </issue>
-
-    <issue
-        id="Range"
-        message="Value must be ≥ 0 (was -1)"
-        errorLine1="                                childHeightSpec = MeasureSpec.makeMeasureSpec(maxLayoutHeight,"
-        errorLine2="                                                                              ~~~~~~~~~~~~~~~">
-        <location
-            file="java/android/support/v4/widget/SlidingPaneLayout.java"
-            line="586"
-            column="79"/>
-    </issue>
-
-    <issue
-        id="Range"
-        message="Value must be ≥ 0 (was -1)"
-        errorLine1="                                childHeightSpec = MeasureSpec.makeMeasureSpec(maxLayoutHeight,"
-        errorLine2="                                                                              ~~~~~~~~~~~~~~~">
-        <location
-            file="java/android/support/v4/widget/SlidingPaneLayout.java"
-            line="589"
-            column="79"/>
-    </issue>
-
-    <issue
-        id="Range"
-        message="Value must be ≥ 0 (was -1)"
-        errorLine1="                            childHeightSpec = MeasureSpec.makeMeasureSpec(maxLayoutHeight,"
-        errorLine2="                                                                          ~~~~~~~~~~~~~~~">
-        <location
-            file="java/android/support/v4/widget/SlidingPaneLayout.java"
-            line="608"
-            column="75"/>
-    </issue>
-
-    <issue
-        id="Range"
-        message="Value must be ≥ 0 (was -1)"
-        errorLine1="                            childHeightSpec = MeasureSpec.makeMeasureSpec(maxLayoutHeight,"
-        errorLine2="                                                                          ~~~~~~~~~~~~~~~">
-        <location
-            file="java/android/support/v4/widget/SlidingPaneLayout.java"
-            line="611"
-            column="75"/>
-    </issue>
+<issues format="4" by="lint 3.0.0-alpha5">
 
     <issue
         id="WrongConstant"
-        message="Must be one of: Gravity.LEFT, Gravity.RIGHT, GravityCompat.START, GravityCompat.END"
-        errorLine1="        setDrawerLockMode(lockMode, gravity);"
-        errorLine2="                                    ~~~~~~~">
+        message="Must be one or more of: Gravity.LEFT, Gravity.RIGHT, GravityCompat.START, GravityCompat.END"
+        errorLine1="                            + gravityToString(childGravity) + &quot; but this &quot; + TAG + &quot; already has a &quot;"
+        errorLine2="                                              ~~~~~~~~~~~~">
         <location
             file="java/android/support/v4/widget/DrawerLayout.java"
-            line="680"
-            column="37"/>
-    </issue>
-
-    <issue
-        id="WrongConstant"
-        message="Must be one of: Gravity.LEFT, Gravity.RIGHT, GravityCompat.START, GravityCompat.END"
-        errorLine1="        return getDrawerLockMode(drawerGravity);"
-        errorLine2="                                 ~~~~~~~~~~~~~">
-        <location
-            file="java/android/support/v4/widget/DrawerLayout.java"
-            line="753"
-            column="34"/>
-    </issue>
-
-    <issue
-        id="WrongConstant"
-        message="Flag not allowed here"
-        errorLine1="                        getDrawerViewAbsoluteGravity(child) &amp; Gravity.HORIZONTAL_GRAVITY_MASK;"
-        errorLine2="                        ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
-        <location
-            file="java/android/support/v4/widget/DrawerLayout.java"
-            line="1086"
-            column="25"/>
+            line="1075"
+            column="47"/>
     </issue>
 
 </issues>
diff --git a/v7/mediarouter/build.gradle b/v7/mediarouter/build.gradle
index 093c6c5..f971a0b 100644
--- a/v7/mediarouter/build.gradle
+++ b/v7/mediarouter/build.gradle
@@ -11,7 +11,7 @@
     androidTestCompile (libs.espresso_core) {
         exclude module: 'support-annotations'
     }
-    androidTestCompile libs.mockito_core
+    androidTestCompile libs.test_rules
 }
 
 android {
diff --git a/v7/mediarouter/lint-baseline.xml b/v7/mediarouter/lint-baseline.xml
index edf2faf..4e530f6 100644
--- a/v7/mediarouter/lint-baseline.xml
+++ b/v7/mediarouter/lint-baseline.xml
@@ -1,5 +1,5 @@
 <?xml version="1.0" encoding="UTF-8"?>
-<issues format="4" by="lint 2.4.0-alpha6">
+<issues format="4" by="lint 3.0.0-alpha5">
 
     <issue
         id="MissingPermission"
@@ -14,28 +14,6 @@
 
     <issue
         id="MissingSuperCall"
-        message="Overriding method should call `super.onAnimationEnd`"
-        errorLine1="                public void onAnimationEnd(Animation animation) {"
-        errorLine2="                            ~~~~~~~~~~~~~~">
-        <location
-            file="java/android/support/v4/app/FragmentManager.java"
-            line="1604"
-            column="29"/>
-    </issue>
-
-    <issue
-        id="MissingSuperCall"
-        message="Overriding method should call `super.onAnimationEnd`"
-        errorLine1="        public void onAnimationEnd(Animation animation) {"
-        errorLine2="                    ~~~~~~~~~~~~~~">
-        <location
-            file="java/android/support/v4/app/FragmentManager.java"
-            line="3935"
-            column="21"/>
-    </issue>
-
-    <issue
-        id="MissingSuperCall"
         message="Overriding method should call `super.jumpDrawablesToCurrentState`"
         errorLine1="    public void jumpDrawablesToCurrentState() {"
         errorLine2="                ~~~~~~~~~~~~~~~~~~~~~~~~~~~">
@@ -105,17 +83,6 @@
     </issue>
 
     <issue
-        id="ResourceType"
-        message="Expected resource of type xml"
-        errorLine1="            final XmlPullParser parser = res.getXml(resId);"
-        errorLine2="                                                    ~~~~~">
-        <location
-            file="src/android/support/graphics/drawable/VectorDrawableCompat.java"
-            line="557"
-            column="53"/>
-    </issue>
-
-    <issue
         id="Range"
         message="Value must be ≥ 0 (was -2147483648)"
         errorLine1="                                MeasureSpec.makeMeasureSpec(largestChildHeight,"
@@ -138,72 +105,6 @@
     </issue>
 
     <issue
-        id="Range"
-        message="Value must be ≥ 0 (was -1)"
-        errorLine1="                childHeightSpec = MeasureSpec.makeMeasureSpec(maxLayoutHeight, MeasureSpec.AT_MOST);"
-        errorLine2="                                                              ~~~~~~~~~~~~~~~">
-        <location
-            file="java/android/support/v4/widget/SlidingPaneLayout.java"
-            line="536"
-            column="63"/>
-    </issue>
-
-    <issue
-        id="Range"
-        message="Value must be ≥ 0 (was -1)"
-        errorLine1="                childHeightSpec = MeasureSpec.makeMeasureSpec(maxLayoutHeight, MeasureSpec.EXACTLY);"
-        errorLine2="                                                              ~~~~~~~~~~~~~~~">
-        <location
-            file="java/android/support/v4/widget/SlidingPaneLayout.java"
-            line="538"
-            column="63"/>
-    </issue>
-
-    <issue
-        id="Range"
-        message="Value must be ≥ 0 (was -1)"
-        errorLine1="                                childHeightSpec = MeasureSpec.makeMeasureSpec(maxLayoutHeight,"
-        errorLine2="                                                                              ~~~~~~~~~~~~~~~">
-        <location
-            file="java/android/support/v4/widget/SlidingPaneLayout.java"
-            line="586"
-            column="79"/>
-    </issue>
-
-    <issue
-        id="Range"
-        message="Value must be ≥ 0 (was -1)"
-        errorLine1="                                childHeightSpec = MeasureSpec.makeMeasureSpec(maxLayoutHeight,"
-        errorLine2="                                                                              ~~~~~~~~~~~~~~~">
-        <location
-            file="java/android/support/v4/widget/SlidingPaneLayout.java"
-            line="589"
-            column="79"/>
-    </issue>
-
-    <issue
-        id="Range"
-        message="Value must be ≥ 0 (was -1)"
-        errorLine1="                            childHeightSpec = MeasureSpec.makeMeasureSpec(maxLayoutHeight,"
-        errorLine2="                                                                          ~~~~~~~~~~~~~~~">
-        <location
-            file="java/android/support/v4/widget/SlidingPaneLayout.java"
-            line="608"
-            column="75"/>
-    </issue>
-
-    <issue
-        id="Range"
-        message="Value must be ≥ 0 (was -1)"
-        errorLine1="                            childHeightSpec = MeasureSpec.makeMeasureSpec(maxLayoutHeight,"
-        errorLine2="                                                                          ~~~~~~~~~~~~~~~">
-        <location
-            file="java/android/support/v4/widget/SlidingPaneLayout.java"
-            line="611"
-            column="75"/>
-    </issue>
-
-    <issue
         id="LongLogTag"
         message="The logging tag can be at most 23 characters, was 30 (ActionBarDrawerToggleHoneycomb)"
         errorLine1="                Log.w(TAG, &quot;Couldn&apos;t set home-as-up indicator via JB-MR2 API&quot;, e);"
@@ -304,39 +205,6 @@
 
     <issue
         id="LongLogTag"
-        message="The logging tag can be at most 23 characters, was 25 (FragmentStatePagerAdapter)"
-        errorLine1="        if (DEBUG) Log.v(TAG, &quot;Adding item #&quot; + position + &quot;: f=&quot; + fragment);"
-        errorLine2="                         ~~~">
-        <location
-            file="java/android/support/v4/app/FragmentStatePagerAdapter.java"
-            line="110"
-            column="26"/>
-    </issue>
-
-    <issue
-        id="LongLogTag"
-        message="The logging tag can be at most 23 characters, was 25 (FragmentStatePagerAdapter)"
-        errorLine1="        if (DEBUG) Log.v(TAG, &quot;Removing item #&quot; + position + &quot;: f=&quot; + object"
-        errorLine2="                         ~~~">
-        <location
-            file="java/android/support/v4/app/FragmentStatePagerAdapter.java"
-            line="135"
-            column="26"/>
-    </issue>
-
-    <issue
-        id="LongLogTag"
-        message="The logging tag can be at most 23 characters, was 25 (FragmentStatePagerAdapter)"
-        errorLine1="                        Log.w(TAG, &quot;Bad fragment at key &quot; + key);"
-        errorLine2="                              ~~~">
-        <location
-            file="java/android/support/v4/app/FragmentStatePagerAdapter.java"
-            line="224"
-            column="31"/>
-    </issue>
-
-    <issue
-        id="LongLogTag"
         message="The logging tag can be at most 23 characters, was 24 (MediaRouteActionProvider)"
         errorLine1="            Log.e(TAG, &quot;onCreateActionView: this ActionProvider is already associated &quot; +"
         errorLine2="                  ~~~">
@@ -347,215 +215,6 @@
     </issue>
 
     <issue
-        id="LongLogTag"
-        message="The logging tag can be at most 23 characters, was 28 (RoundedBitmapDrawableFactory)"
-        errorLine1="            Log.w(TAG, &quot;RoundedBitmapDrawable cannot decode &quot; + filepath);"
-        errorLine2="                  ~~~">
-        <location
-            file="java/android/support/v4/graphics/drawable/RoundedBitmapDrawableFactory.java"
-            line="80"
-            column="19"/>
-    </issue>
-
-    <issue
-        id="LongLogTag"
-        message="The logging tag can be at most 23 characters, was 28 (RoundedBitmapDrawableFactory)"
-        errorLine1="            Log.w(TAG, &quot;RoundedBitmapDrawable cannot decode &quot; + is);"
-        errorLine2="                  ~~~">
-        <location
-            file="java/android/support/v4/graphics/drawable/RoundedBitmapDrawableFactory.java"
-            line="93"
-            column="19"/>
-    </issue>
-
-    <issue
-        id="LongLogTag"
-        message="The logging tag can be at most 23 characters, was 24 (WakefulBroadcastReceiver)"
-        errorLine1="            Log.w(&quot;WakefulBroadcastReceiver&quot;, &quot;No active wake lock id #&quot; + id);"
-        errorLine2="                  ~~~~~~~~~~~~~~~~~~~~~~~~~~">
-        <location
-            file="java/android/support/v4/content/WakefulBroadcastReceiver.java"
-            line="141"
-            column="19"/>
-    </issue>
-
-    <issue
-        id="AppCompatCustomView"
-        message="This custom view should extend `android.support.v7.widget.AppCompatAutoCompleteTextView` instead"
-        errorLine1="public class AppCompatAutoCompleteTextView extends AutoCompleteTextView implements"
-        errorLine2="                                                   ~~~~~~~~~~~~~~~~~~~~">
-        <location
-            file="src/android/support/v7/widget/AppCompatAutoCompleteTextView.java"
-            line="49"
-            column="52"/>
-    </issue>
-
-    <issue
-        id="AppCompatCustomView"
-        message="This custom view should extend `android.support.v7.widget.AppCompatButton` instead"
-        errorLine1="public class AppCompatButton extends Button implements TintableBackgroundView {"
-        errorLine2="                                     ~~~~~~">
-        <location
-            file="src/android/support/v7/widget/AppCompatButton.java"
-            line="51"
-            column="38"/>
-    </issue>
-
-    <issue
-        id="AppCompatCustomView"
-        message="This custom view should extend `android.support.v7.widget.AppCompatCheckBox` instead"
-        errorLine1="public class AppCompatCheckBox extends CheckBox implements TintableCompoundButton {"
-        errorLine2="                                       ~~~~~~~~">
-        <location
-            file="src/android/support/v7/widget/AppCompatCheckBox.java"
-            line="49"
-            column="40"/>
-    </issue>
-
-    <issue
-        id="AppCompatCustomView"
-        message="This custom view should extend `android.support.v7.widget.AppCompatCheckedTextView` instead"
-        errorLine1="public class AppCompatCheckedTextView extends CheckedTextView {"
-        errorLine2="                                              ~~~~~~~~~~~~~~~">
-        <location
-            file="src/android/support/v7/widget/AppCompatCheckedTextView.java"
-            line="33"
-            column="47"/>
-    </issue>
-
-    <issue
-        id="AppCompatCustomView"
-        message="This custom view should extend `android.support.v7.widget.AppCompatEditText` instead"
-        errorLine1="public class AppCompatEditText extends EditText implements TintableBackgroundView {"
-        errorLine2="                                       ~~~~~~~~">
-        <location
-            file="src/android/support/v7/widget/AppCompatEditText.java"
-            line="48"
-            column="40"/>
-    </issue>
-
-    <issue
-        id="AppCompatCustomView"
-        message="This custom view should extend `android.support.v7.widget.AppCompatImageButton` instead"
-        errorLine1="public class AppCompatImageButton extends ImageButton implements TintableBackgroundView,"
-        errorLine2="                                          ~~~~~~~~~~~">
-        <location
-            file="src/android/support/v7/widget/AppCompatImageButton.java"
-            line="58"
-            column="43"/>
-    </issue>
-
-    <issue
-        id="AppCompatCustomView"
-        message="This custom view should extend `android.support.v7.widget.AppCompatImageView` instead"
-        errorLine1="public class AppCompatImageView extends ImageView implements TintableBackgroundView,"
-        errorLine2="                                        ~~~~~~~~~">
-        <location
-            file="src/android/support/v7/widget/AppCompatImageView.java"
-            line="57"
-            column="41"/>
-    </issue>
-
-    <issue
-        id="AppCompatCustomView"
-        message="This custom view should extend `android.support.v7.widget.AppCompatMultiAutoCompleteTextView` instead"
-        errorLine1="public class AppCompatMultiAutoCompleteTextView extends MultiAutoCompleteTextView"
-        errorLine2="                                                        ~~~~~~~~~~~~~~~~~~~~~~~~~">
-        <location
-            file="src/android/support/v7/widget/AppCompatMultiAutoCompleteTextView.java"
-            line="49"
-            column="57"/>
-    </issue>
-
-    <issue
-        id="AppCompatCustomView"
-        message="This custom view should extend `android.support.v7.widget.AppCompatRadioButton` instead"
-        errorLine1="public class AppCompatRadioButton extends RadioButton implements TintableCompoundButton {"
-        errorLine2="                                          ~~~~~~~~~~~">
-        <location
-            file="src/android/support/v7/widget/AppCompatRadioButton.java"
-            line="49"
-            column="43"/>
-    </issue>
-
-    <issue
-        id="AppCompatCustomView"
-        message="This custom view should extend `android.support.v7.widget.AppCompatRatingBar` instead"
-        errorLine1="public class AppCompatRatingBar extends RatingBar {"
-        errorLine2="                                        ~~~~~~~~~">
-        <location
-            file="src/android/support/v7/widget/AppCompatRatingBar.java"
-            line="34"
-            column="41"/>
-    </issue>
-
-    <issue
-        id="AppCompatCustomView"
-        message="This custom view should extend `android.support.v7.widget.AppCompatSeekBar` instead"
-        errorLine1="public class AppCompatSeekBar extends SeekBar {"
-        errorLine2="                                      ~~~~~~~">
-        <location
-            file="src/android/support/v7/widget/AppCompatSeekBar.java"
-            line="34"
-            column="39"/>
-    </issue>
-
-    <issue
-        id="AppCompatCustomView"
-        message="This custom view should extend `android.support.v7.widget.AppCompatSpinner` instead"
-        errorLine1="public class AppCompatSpinner extends Spinner implements TintableBackgroundView {"
-        errorLine2="                                      ~~~~~~~">
-        <location
-            file="src/android/support/v7/widget/AppCompatSpinner.java"
-            line="68"
-            column="39"/>
-    </issue>
-
-    <issue
-        id="AppCompatCustomView"
-        message="This custom view should extend `android.support.v7.widget.AppCompatTextView` instead"
-        errorLine1="public class AppCompatTextView extends TextView implements TintableBackgroundView,"
-        errorLine2="                                       ~~~~~~~~">
-        <location
-            file="src/android/support/v7/widget/AppCompatTextView.java"
-            line="60"
-            column="40"/>
-    </issue>
-
-    <issue
-        id="AppCompatCustomView"
-        message="This custom view should extend `android.support.v7.widget.AppCompatImageView` instead"
-        errorLine1="class CircleImageView extends ImageView {"
-        errorLine2="                              ~~~~~~~~~">
-        <location
-            file="java/android/support/v4/widget/CircleImageView.java"
-            line="38"
-            column="31"/>
-    </issue>
-
-    <issue
-        id="AppCompatCustomView"
-        message="This custom view should extend `android.support.v7.widget.AppCompatTextView` instead"
-        errorLine1="public class DialogTitle extends TextView {"
-        errorLine2="                                 ~~~~~~~~">
-        <location
-            file="src/android/support/v7/widget/DialogTitle.java"
-            line="37"
-            column="34"/>
-    </issue>
-
-    <issue
-        id="AppCompatCustomView"
-        message="This custom view should extend `android.support.v7.widget.AppCompatImageButton` instead"
-        errorLine1="class MediaRouteExpandCollapseButton extends ImageButton {"
-        errorLine2="                                             ~~~~~~~~~~~">
-        <location
-            file="src/android/support/v7/app/MediaRouteExpandCollapseButton.java"
-            line="33"
-            column="46"/>
-    </issue>
-
-    <issue
         id="WrongConstant"
         message="Must be one of: PixelFormat.UNKNOWN, PixelFormat.TRANSLUCENT, PixelFormat.TRANSPARENT, PixelFormat.OPAQUE"
         errorLine1="        return 0;"
@@ -568,35 +227,13 @@
 
     <issue
         id="WrongConstant"
-        message="Must be one of: Gravity.LEFT, Gravity.RIGHT, GravityCompat.START, GravityCompat.END"
-        errorLine1="        setDrawerLockMode(lockMode, gravity);"
-        errorLine2="                                    ~~~~~~~">
+        message="Must be one or more of: Gravity.LEFT, Gravity.RIGHT, GravityCompat.START, GravityCompat.END"
+        errorLine1="                            + gravityToString(childGravity) + &quot; but this &quot; + TAG + &quot; already has a &quot;"
+        errorLine2="                                              ~~~~~~~~~~~~">
         <location
             file="java/android/support/v4/widget/DrawerLayout.java"
-            line="680"
-            column="37"/>
-    </issue>
-
-    <issue
-        id="WrongConstant"
-        message="Must be one of: Gravity.LEFT, Gravity.RIGHT, GravityCompat.START, GravityCompat.END"
-        errorLine1="        return getDrawerLockMode(drawerGravity);"
-        errorLine2="                                 ~~~~~~~~~~~~~">
-        <location
-            file="java/android/support/v4/widget/DrawerLayout.java"
-            line="753"
-            column="34"/>
-    </issue>
-
-    <issue
-        id="WrongConstant"
-        message="Flag not allowed here"
-        errorLine1="                        getDrawerViewAbsoluteGravity(child) &amp; Gravity.HORIZONTAL_GRAVITY_MASK;"
-        errorLine2="                        ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
-        <location
-            file="java/android/support/v4/widget/DrawerLayout.java"
-            line="1086"
-            column="25"/>
+            line="1075"
+            column="47"/>
     </issue>
 
     <issue
@@ -621,15 +258,4 @@
             column="36"/>
     </issue>
 
-    <issue
-        id="WrongConstant"
-        message="Must be one of: View.LAYOUT_DIRECTION_LTR, View.LAYOUT_DIRECTION_RTL"
-        errorLine1="            return isAutoMirrored() &amp;&amp; getLayoutDirection() == LayoutDirection.RTL;"
-        errorLine2="                                                               ~~~~~~~~~~~~~~~~~~~">
-        <location
-            file="src/android/support/graphics/drawable/VectorDrawableCompat.java"
-            line="821"
-            column="64"/>
-    </issue>
-
 </issues>
diff --git a/v7/mediarouter/src/android/support/v7/app/MediaRouteControllerDialog.java b/v7/mediarouter/src/android/support/v7/app/MediaRouteControllerDialog.java
index 86f4753..1f17519 100644
--- a/v7/mediarouter/src/android/support/v7/app/MediaRouteControllerDialog.java
+++ b/v7/mediarouter/src/android/support/v7/app/MediaRouteControllerDialog.java
@@ -20,7 +20,6 @@
 import static android.support.v4.media.session.PlaybackStateCompat.ACTION_PLAY;
 import static android.support.v4.media.session.PlaybackStateCompat.ACTION_PLAY_PAUSE;
 import static android.support.v4.media.session.PlaybackStateCompat.ACTION_STOP;
-import static android.support.v4.utils.ObjectUtils.objectEquals;
 
 import android.app.PendingIntent;
 import android.content.ContentResolver;
@@ -40,6 +39,7 @@
 import android.support.v4.media.session.MediaControllerCompat;
 import android.support.v4.media.session.MediaSessionCompat;
 import android.support.v4.media.session.PlaybackStateCompat;
+import android.support.v4.util.ObjectsCompat;
 import android.support.v4.view.accessibility.AccessibilityEventCompat;
 import android.support.v7.app.OverlayListView.OverlayObject;
 import android.support.v7.graphics.Palette;
@@ -1444,8 +1444,8 @@
         @Override
         protected void onPostExecute(Bitmap art) {
             mFetchArtTask = null;
-            if (!objectEquals(mArtIconBitmap, mIconBitmap)
-                    || !objectEquals(mArtIconUri, mIconUri)) {
+            if (!ObjectsCompat.equals(mArtIconBitmap, mIconBitmap)
+                    || !ObjectsCompat.equals(mArtIconUri, mIconUri)) {
                 mArtIconBitmap = mIconBitmap;
                 mArtIconLoadedBitmap = art;
                 mArtIconUri = mIconUri;
diff --git a/v7/mediarouter/src/android/support/v7/media/MediaRouteProvider.java b/v7/mediarouter/src/android/support/v7/media/MediaRouteProvider.java
index 5467c40..99bcf21 100644
--- a/v7/mediarouter/src/android/support/v7/media/MediaRouteProvider.java
+++ b/v7/mediarouter/src/android/support/v7/media/MediaRouteProvider.java
@@ -17,7 +17,6 @@
 package android.support.v7.media;
 
 import static android.support.annotation.RestrictTo.Scope.LIBRARY_GROUP;
-import static android.support.v4.utils.ObjectUtils.objectEquals;
 
 import android.content.ComponentName;
 import android.content.Context;
@@ -27,6 +26,7 @@
 import android.support.annotation.NonNull;
 import android.support.annotation.Nullable;
 import android.support.annotation.RestrictTo;
+import android.support.v4.util.ObjectsCompat;
 import android.support.v7.media.MediaRouter.ControlRequestCallback;
 
 /**
@@ -151,7 +151,7 @@
     public final void setDiscoveryRequest(MediaRouteDiscoveryRequest request) {
         MediaRouter.checkCallingThread();
 
-        if (objectEquals(mDiscoveryRequest, request)) {
+        if (ObjectsCompat.equals(mDiscoveryRequest, request)) {
             return;
         }
 
diff --git a/v7/mediarouter/src/android/support/v7/media/MediaRouteProviderService.java b/v7/mediarouter/src/android/support/v7/media/MediaRouteProviderService.java
index d788a3c..faa211d 100644
--- a/v7/mediarouter/src/android/support/v7/media/MediaRouteProviderService.java
+++ b/v7/mediarouter/src/android/support/v7/media/MediaRouteProviderService.java
@@ -16,7 +16,6 @@
 
 package android.support.v7.media;
 
-import static android.support.v4.utils.ObjectUtils.objectEquals;
 import static android.support.v7.media.MediaRouteProviderProtocol.CLIENT_DATA_ROUTE_ID;
 import static android.support.v7.media.MediaRouteProviderProtocol.CLIENT_DATA_ROUTE_LIBRARY_GROUP;
 import static android.support.v7.media.MediaRouteProviderProtocol.CLIENT_DATA_UNSELECT_REASON;
@@ -57,6 +56,7 @@
 import android.os.Messenger;
 import android.os.RemoteException;
 import android.support.annotation.VisibleForTesting;
+import android.support.v4.util.ObjectsCompat;
 import android.util.Log;
 import android.util.SparseArray;
 
@@ -462,7 +462,7 @@
         if (selectorBuilder != null) {
             composite = new MediaRouteDiscoveryRequest(selectorBuilder.build(), activeScan);
         }
-        if (!objectEquals(mCompositeDiscoveryRequest, composite)) {
+        if (!ObjectsCompat.equals(mCompositeDiscoveryRequest, composite)) {
             mCompositeDiscoveryRequest = composite;
             mProvider.setDiscoveryRequest(composite);
             return true;
@@ -612,7 +612,7 @@
         }
 
         public boolean setDiscoveryRequest(MediaRouteDiscoveryRequest request) {
-            if (!objectEquals(mDiscoveryRequest, request)) {
+            if (!ObjectsCompat.equals(mDiscoveryRequest, request)) {
                 mDiscoveryRequest = request;
                 return updateCompositeDiscoveryRequest();
             }
diff --git a/v7/mediarouter/src/android/support/v7/media/MediaRouter.java b/v7/mediarouter/src/android/support/v7/media/MediaRouter.java
index 8b64688..cc5648f 100644
--- a/v7/mediarouter/src/android/support/v7/media/MediaRouter.java
+++ b/v7/mediarouter/src/android/support/v7/media/MediaRouter.java
@@ -2562,7 +2562,7 @@
 
         private void setSelectedRouteInternal(RouteInfo route, int unselectReason) {
             // TODO: Remove the following logging when no longer needed.
-            if (mBluetoothRoute != null && route != null && route.isDefault()) {
+            if (sGlobal == null || mBluetoothRoute != null && route != null && route.isDefault()) {
                 final StackTraceElement[] callStack = Thread.currentThread().getStackTrace();
                 StringBuffer sb = new StringBuffer();
                 // callStack[3] is the caller of this method.
@@ -2571,8 +2571,13 @@
                     sb.append(caller.getClassName() + "." + caller.getMethodName()
                             + ":" + caller.getLineNumber()).append("  ");
                 }
-                Log.w(TAG, "Default route is selected while a BT route is available: pkgName="
-                        + mApplicationContext.getPackageName() + ", callers=" + sb.toString());
+                if (sGlobal == null) {
+                    Log.w(TAG, "setSelectedRouteInternal is called while sGlobal is null: pkgName="
+                            + mApplicationContext.getPackageName() + ", callers=" + sb.toString());
+                } else {
+                    Log.w(TAG, "Default route is selected while a BT route is available: pkgName="
+                            + mApplicationContext.getPackageName() + ", callers=" + sb.toString());
+                }
             }
 
             if (mSelectedRoute != route) {
diff --git a/v7/mediarouter/src/android/support/v7/media/RemotePlaybackClient.java b/v7/mediarouter/src/android/support/v7/media/RemotePlaybackClient.java
index 045b69b..fb05ac7 100644
--- a/v7/mediarouter/src/android/support/v7/media/RemotePlaybackClient.java
+++ b/v7/mediarouter/src/android/support/v7/media/RemotePlaybackClient.java
@@ -15,8 +15,6 @@
  */
 package android.support.v7.media;
 
-import static android.support.v4.utils.ObjectUtils.objectEquals;
-
 import android.app.PendingIntent;
 import android.content.BroadcastReceiver;
 import android.content.Context;
@@ -24,6 +22,7 @@
 import android.content.IntentFilter;
 import android.net.Uri;
 import android.os.Bundle;
+import android.support.v4.util.ObjectsCompat;
 import android.util.Log;
 
 /**
@@ -204,7 +203,7 @@
      * @param sessionId The new session id, or null if none.
      */
     public void setSessionId(String sessionId) {
-        if (!objectEquals(mSessionId, sessionId)) {
+        if (!ObjectsCompat.equals(mSessionId, sessionId)) {
             if (DEBUG) {
                 Log.d(TAG, "Session id is now: " + sessionId);
             }
diff --git a/v7/palette/lint-baseline.xml b/v7/palette/lint-baseline.xml
index d75d39e..fb44511 100644
--- a/v7/palette/lint-baseline.xml
+++ b/v7/palette/lint-baseline.xml
@@ -1,37 +1,4 @@
 <?xml version="1.0" encoding="UTF-8"?>
-<issues format="4" by="lint 2.4.0-alpha6">
-
-    <issue
-        id="LongLogTag"
-        message="The logging tag can be at most 23 characters, was 28 (RoundedBitmapDrawableFactory)"
-        errorLine1="            Log.w(TAG, &quot;RoundedBitmapDrawable cannot decode &quot; + filepath);"
-        errorLine2="                  ~~~">
-        <location
-            file="java/android/support/v4/graphics/drawable/RoundedBitmapDrawableFactory.java"
-            line="80"
-            column="19"/>
-    </issue>
-
-    <issue
-        id="LongLogTag"
-        message="The logging tag can be at most 23 characters, was 28 (RoundedBitmapDrawableFactory)"
-        errorLine1="            Log.w(TAG, &quot;RoundedBitmapDrawable cannot decode &quot; + is);"
-        errorLine2="                  ~~~">
-        <location
-            file="java/android/support/v4/graphics/drawable/RoundedBitmapDrawableFactory.java"
-            line="93"
-            column="19"/>
-    </issue>
-
-    <issue
-        id="LongLogTag"
-        message="The logging tag can be at most 23 characters, was 24 (WakefulBroadcastReceiver)"
-        errorLine1="            Log.w(&quot;WakefulBroadcastReceiver&quot;, &quot;No active wake lock id #&quot; + id);"
-        errorLine2="                  ~~~~~~~~~~~~~~~~~~~~~~~~~~">
-        <location
-            file="java/android/support/v4/content/WakefulBroadcastReceiver.java"
-            line="141"
-            column="19"/>
-    </issue>
+<issues format="4" by="lint 3.0.0-alpha5">
 
 </issues>
diff --git a/v7/preference/lint-baseline.xml b/v7/preference/lint-baseline.xml
index c9f4dab..5f43434 100644
--- a/v7/preference/lint-baseline.xml
+++ b/v7/preference/lint-baseline.xml
@@ -1,5 +1,5 @@
 <?xml version="1.0" encoding="UTF-8"?>
-<issues format="4" by="lint 2.4.0-alpha6">
+<issues format="4" by="lint 3.0.0-alpha5">
 
     <issue
         id="MissingPermission"
@@ -14,28 +14,6 @@
 
     <issue
         id="MissingSuperCall"
-        message="Overriding method should call `super.onAnimationEnd`"
-        errorLine1="                public void onAnimationEnd(Animation animation) {"
-        errorLine2="                            ~~~~~~~~~~~~~~">
-        <location
-            file="java/android/support/v4/app/FragmentManager.java"
-            line="1604"
-            column="29"/>
-    </issue>
-
-    <issue
-        id="MissingSuperCall"
-        message="Overriding method should call `super.onAnimationEnd`"
-        errorLine1="        public void onAnimationEnd(Animation animation) {"
-        errorLine2="                    ~~~~~~~~~~~~~~">
-        <location
-            file="java/android/support/v4/app/FragmentManager.java"
-            line="3935"
-            column="21"/>
-    </issue>
-
-    <issue
-        id="MissingSuperCall"
         message="Overriding method should call `super.draw`"
         errorLine1="    public void draw(Canvas canvas) {"
         errorLine2="                ~~~~">
@@ -94,17 +72,6 @@
     </issue>
 
     <issue
-        id="ResourceType"
-        message="Expected resource of type xml"
-        errorLine1="            final XmlPullParser parser = res.getXml(resId);"
-        errorLine2="                                                    ~~~~~">
-        <location
-            file="src/android/support/graphics/drawable/VectorDrawableCompat.java"
-            line="557"
-            column="53"/>
-    </issue>
-
-    <issue
         id="Range"
         message="Value must be ≥ 0 (was -2147483648)"
         errorLine1="                                MeasureSpec.makeMeasureSpec(largestChildHeight,"
@@ -127,72 +94,6 @@
     </issue>
 
     <issue
-        id="Range"
-        message="Value must be ≥ 0 (was -1)"
-        errorLine1="                childHeightSpec = MeasureSpec.makeMeasureSpec(maxLayoutHeight, MeasureSpec.AT_MOST);"
-        errorLine2="                                                              ~~~~~~~~~~~~~~~">
-        <location
-            file="java/android/support/v4/widget/SlidingPaneLayout.java"
-            line="536"
-            column="63"/>
-    </issue>
-
-    <issue
-        id="Range"
-        message="Value must be ≥ 0 (was -1)"
-        errorLine1="                childHeightSpec = MeasureSpec.makeMeasureSpec(maxLayoutHeight, MeasureSpec.EXACTLY);"
-        errorLine2="                                                              ~~~~~~~~~~~~~~~">
-        <location
-            file="java/android/support/v4/widget/SlidingPaneLayout.java"
-            line="538"
-            column="63"/>
-    </issue>
-
-    <issue
-        id="Range"
-        message="Value must be ≥ 0 (was -1)"
-        errorLine1="                                childHeightSpec = MeasureSpec.makeMeasureSpec(maxLayoutHeight,"
-        errorLine2="                                                                              ~~~~~~~~~~~~~~~">
-        <location
-            file="java/android/support/v4/widget/SlidingPaneLayout.java"
-            line="586"
-            column="79"/>
-    </issue>
-
-    <issue
-        id="Range"
-        message="Value must be ≥ 0 (was -1)"
-        errorLine1="                                childHeightSpec = MeasureSpec.makeMeasureSpec(maxLayoutHeight,"
-        errorLine2="                                                                              ~~~~~~~~~~~~~~~">
-        <location
-            file="java/android/support/v4/widget/SlidingPaneLayout.java"
-            line="589"
-            column="79"/>
-    </issue>
-
-    <issue
-        id="Range"
-        message="Value must be ≥ 0 (was -1)"
-        errorLine1="                            childHeightSpec = MeasureSpec.makeMeasureSpec(maxLayoutHeight,"
-        errorLine2="                                                                          ~~~~~~~~~~~~~~~">
-        <location
-            file="java/android/support/v4/widget/SlidingPaneLayout.java"
-            line="608"
-            column="75"/>
-    </issue>
-
-    <issue
-        id="Range"
-        message="Value must be ≥ 0 (was -1)"
-        errorLine1="                            childHeightSpec = MeasureSpec.makeMeasureSpec(maxLayoutHeight,"
-        errorLine2="                                                                          ~~~~~~~~~~~~~~~">
-        <location
-            file="java/android/support/v4/widget/SlidingPaneLayout.java"
-            line="611"
-            column="75"/>
-    </issue>
-
-    <issue
         id="Suspicious0dp"
         message="Suspicious size: this will make the view invisible, should be used with `layout_weight`"
         errorLine1="        android:layout_width=&quot;0dp&quot;"
@@ -314,362 +215,6 @@
     </issue>
 
     <issue
-        id="LongLogTag"
-        message="The logging tag can be at most 23 characters, was 25 (FragmentStatePagerAdapter)"
-        errorLine1="        if (DEBUG) Log.v(TAG, &quot;Adding item #&quot; + position + &quot;: f=&quot; + fragment);"
-        errorLine2="                         ~~~">
-        <location
-            file="java/android/support/v4/app/FragmentStatePagerAdapter.java"
-            line="110"
-            column="26"/>
-    </issue>
-
-    <issue
-        id="LongLogTag"
-        message="The logging tag can be at most 23 characters, was 25 (FragmentStatePagerAdapter)"
-        errorLine1="        if (DEBUG) Log.v(TAG, &quot;Removing item #&quot; + position + &quot;: f=&quot; + object"
-        errorLine2="                         ~~~">
-        <location
-            file="java/android/support/v4/app/FragmentStatePagerAdapter.java"
-            line="135"
-            column="26"/>
-    </issue>
-
-    <issue
-        id="LongLogTag"
-        message="The logging tag can be at most 23 characters, was 25 (FragmentStatePagerAdapter)"
-        errorLine1="                        Log.w(TAG, &quot;Bad fragment at key &quot; + key);"
-        errorLine2="                              ~~~">
-        <location
-            file="java/android/support/v4/app/FragmentStatePagerAdapter.java"
-            line="224"
-            column="31"/>
-    </issue>
-
-    <issue
-        id="LongLogTag"
-        message="The logging tag can be at most 23 characters, was 28 (RoundedBitmapDrawableFactory)"
-        errorLine1="            Log.w(TAG, &quot;RoundedBitmapDrawable cannot decode &quot; + filepath);"
-        errorLine2="                  ~~~">
-        <location
-            file="java/android/support/v4/graphics/drawable/RoundedBitmapDrawableFactory.java"
-            line="80"
-            column="19"/>
-    </issue>
-
-    <issue
-        id="LongLogTag"
-        message="The logging tag can be at most 23 characters, was 28 (RoundedBitmapDrawableFactory)"
-        errorLine1="            Log.w(TAG, &quot;RoundedBitmapDrawable cannot decode &quot; + is);"
-        errorLine2="                  ~~~">
-        <location
-            file="java/android/support/v4/graphics/drawable/RoundedBitmapDrawableFactory.java"
-            line="93"
-            column="19"/>
-    </issue>
-
-    <issue
-        id="LongLogTag"
-        message="The logging tag can be at most 23 characters, was 26 (StaggeredGridLayoutManager)"
-        errorLine1="            Log.d(TAG, &quot;found saved state: &quot; + mPendingSavedState);"
-        errorLine2="                  ~~~">
-        <location
-            file="src/android/support/v7/widget/StaggeredGridLayoutManager.java"
-            line="783"
-            column="19"/>
-    </issue>
-
-    <issue
-        id="LongLogTag"
-        message="The logging tag can be at most 23 characters, was 26 (StaggeredGridLayoutManager)"
-        errorLine1="            Log.d(TAG, &quot;Deciding anchor info from fresh state&quot;);"
-        errorLine2="                  ~~~">
-        <location
-            file="src/android/support/v7/widget/StaggeredGridLayoutManager.java"
-            line="828"
-            column="19"/>
-    </issue>
-
-    <issue
-        id="LongLogTag"
-        message="The logging tag can be at most 23 characters, was 26 (StaggeredGridLayoutManager)"
-        errorLine1="            Log.d(TAG, &quot;invalid saved state class&quot;);"
-        errorLine2="                  ~~~">
-        <location
-            file="src/android/support/v7/widget/StaggeredGridLayoutManager.java"
-            line="1187"
-            column="19"/>
-    </issue>
-
-    <issue
-        id="LongLogTag"
-        message="The logging tag can be at most 23 characters, was 26 (StaggeredGridLayoutManager)"
-        errorLine1="            Log.d(TAG, &quot;saved state:\n&quot; + state);"
-        errorLine2="                  ~~~">
-        <location
-            file="src/android/support/v7/widget/StaggeredGridLayoutManager.java"
-            line="1236"
-            column="19"/>
-    </issue>
-
-    <issue
-        id="LongLogTag"
-        message="The logging tag can be at most 23 characters, was 26 (StaggeredGridLayoutManager)"
-        errorLine1="            Log.d(TAG, &quot;FILLING targetLine: &quot; + targetLine + &quot;,&quot;"
-        errorLine2="                  ~~~">
-        <location
-            file="src/android/support/v7/widget/StaggeredGridLayoutManager.java"
-            line="1559"
-            column="19"/>
-    </issue>
-
-    <issue
-        id="LongLogTag"
-        message="The logging tag can be at most 23 characters, was 26 (StaggeredGridLayoutManager)"
-        errorLine1="                    Log.d(TAG, &quot;assigned &quot; + currentSpan.mIndex + &quot; for &quot; + position);"
-        errorLine2="                          ~~~">
-        <location
-            file="src/android/support/v7/widget/StaggeredGridLayoutManager.java"
-            line="1580"
-            column="27"/>
-    </issue>
-
-    <issue
-        id="LongLogTag"
-        message="The logging tag can be at most 23 characters, was 26 (StaggeredGridLayoutManager)"
-        errorLine1="                    Log.d(TAG, &quot;using &quot; + spanIndex + &quot; for pos &quot; + position);"
-        errorLine2="                          ~~~">
-        <location
-            file="src/android/support/v7/widget/StaggeredGridLayoutManager.java"
-            line="1584"
-            column="27"/>
-    </issue>
-
-    <issue
-        id="LongLogTag"
-        message="The logging tag can be at most 23 characters, was 26 (StaggeredGridLayoutManager)"
-        errorLine1="            Log.d(TAG, &quot;asked &quot; + dt + &quot; scrolled&quot; + totalScroll);"
-        errorLine2="                  ~~~">
-        <location
-            file="src/android/support/v7/widget/StaggeredGridLayoutManager.java"
-            line="2153"
-            column="19"/>
-    </issue>
-
-    <issue
-        id="LongLogTag"
-        message="The logging tag can be at most 23 characters, was 26 (StaggeredGridLayoutManager)"
-        errorLine1="                    Log.d(TAG, &quot;Unknown focus request:&quot; + focusDirection);"
-        errorLine2="                          ~~~">
-        <location
-            file="src/android/support/v7/widget/StaggeredGridLayoutManager.java"
-            line="2385"
-            column="27"/>
-    </issue>
-
-    <issue
-        id="LongLogTag"
-        message="The logging tag can be at most 23 characters, was 24 (WakefulBroadcastReceiver)"
-        errorLine1="            Log.w(&quot;WakefulBroadcastReceiver&quot;, &quot;No active wake lock id #&quot; + id);"
-        errorLine2="                  ~~~~~~~~~~~~~~~~~~~~~~~~~~">
-        <location
-            file="java/android/support/v4/content/WakefulBroadcastReceiver.java"
-            line="141"
-            column="19"/>
-    </issue>
-
-    <issue
-        id="AppCompatCustomView"
-        message="This custom view should extend `android.support.v7.widget.AppCompatAutoCompleteTextView` instead"
-        errorLine1="public class AppCompatAutoCompleteTextView extends AutoCompleteTextView implements"
-        errorLine2="                                                   ~~~~~~~~~~~~~~~~~~~~">
-        <location
-            file="src/android/support/v7/widget/AppCompatAutoCompleteTextView.java"
-            line="49"
-            column="52"/>
-    </issue>
-
-    <issue
-        id="AppCompatCustomView"
-        message="This custom view should extend `android.support.v7.widget.AppCompatButton` instead"
-        errorLine1="public class AppCompatButton extends Button implements TintableBackgroundView {"
-        errorLine2="                                     ~~~~~~">
-        <location
-            file="src/android/support/v7/widget/AppCompatButton.java"
-            line="51"
-            column="38"/>
-    </issue>
-
-    <issue
-        id="AppCompatCustomView"
-        message="This custom view should extend `android.support.v7.widget.AppCompatCheckBox` instead"
-        errorLine1="public class AppCompatCheckBox extends CheckBox implements TintableCompoundButton {"
-        errorLine2="                                       ~~~~~~~~">
-        <location
-            file="src/android/support/v7/widget/AppCompatCheckBox.java"
-            line="49"
-            column="40"/>
-    </issue>
-
-    <issue
-        id="AppCompatCustomView"
-        message="This custom view should extend `android.support.v7.widget.AppCompatCheckedTextView` instead"
-        errorLine1="public class AppCompatCheckedTextView extends CheckedTextView {"
-        errorLine2="                                              ~~~~~~~~~~~~~~~">
-        <location
-            file="src/android/support/v7/widget/AppCompatCheckedTextView.java"
-            line="33"
-            column="47"/>
-    </issue>
-
-    <issue
-        id="AppCompatCustomView"
-        message="This custom view should extend `android.support.v7.widget.AppCompatEditText` instead"
-        errorLine1="public class AppCompatEditText extends EditText implements TintableBackgroundView {"
-        errorLine2="                                       ~~~~~~~~">
-        <location
-            file="src/android/support/v7/widget/AppCompatEditText.java"
-            line="48"
-            column="40"/>
-    </issue>
-
-    <issue
-        id="AppCompatCustomView"
-        message="This custom view should extend `android.support.v7.widget.AppCompatImageButton` instead"
-        errorLine1="public class AppCompatImageButton extends ImageButton implements TintableBackgroundView,"
-        errorLine2="                                          ~~~~~~~~~~~">
-        <location
-            file="src/android/support/v7/widget/AppCompatImageButton.java"
-            line="58"
-            column="43"/>
-    </issue>
-
-    <issue
-        id="AppCompatCustomView"
-        message="This custom view should extend `android.support.v7.widget.AppCompatImageView` instead"
-        errorLine1="public class AppCompatImageView extends ImageView implements TintableBackgroundView,"
-        errorLine2="                                        ~~~~~~~~~">
-        <location
-            file="src/android/support/v7/widget/AppCompatImageView.java"
-            line="57"
-            column="41"/>
-    </issue>
-
-    <issue
-        id="AppCompatCustomView"
-        message="This custom view should extend `android.support.v7.widget.AppCompatMultiAutoCompleteTextView` instead"
-        errorLine1="public class AppCompatMultiAutoCompleteTextView extends MultiAutoCompleteTextView"
-        errorLine2="                                                        ~~~~~~~~~~~~~~~~~~~~~~~~~">
-        <location
-            file="src/android/support/v7/widget/AppCompatMultiAutoCompleteTextView.java"
-            line="49"
-            column="57"/>
-    </issue>
-
-    <issue
-        id="AppCompatCustomView"
-        message="This custom view should extend `android.support.v7.widget.AppCompatRadioButton` instead"
-        errorLine1="public class AppCompatRadioButton extends RadioButton implements TintableCompoundButton {"
-        errorLine2="                                          ~~~~~~~~~~~">
-        <location
-            file="src/android/support/v7/widget/AppCompatRadioButton.java"
-            line="49"
-            column="43"/>
-    </issue>
-
-    <issue
-        id="AppCompatCustomView"
-        message="This custom view should extend `android.support.v7.widget.AppCompatRatingBar` instead"
-        errorLine1="public class AppCompatRatingBar extends RatingBar {"
-        errorLine2="                                        ~~~~~~~~~">
-        <location
-            file="src/android/support/v7/widget/AppCompatRatingBar.java"
-            line="34"
-            column="41"/>
-    </issue>
-
-    <issue
-        id="AppCompatCustomView"
-        message="This custom view should extend `android.support.v7.widget.AppCompatSeekBar` instead"
-        errorLine1="public class AppCompatSeekBar extends SeekBar {"
-        errorLine2="                                      ~~~~~~~">
-        <location
-            file="src/android/support/v7/widget/AppCompatSeekBar.java"
-            line="34"
-            column="39"/>
-    </issue>
-
-    <issue
-        id="AppCompatCustomView"
-        message="This custom view should extend `android.support.v7.widget.AppCompatSpinner` instead"
-        errorLine1="public class AppCompatSpinner extends Spinner implements TintableBackgroundView {"
-        errorLine2="                                      ~~~~~~~">
-        <location
-            file="src/android/support/v7/widget/AppCompatSpinner.java"
-            line="68"
-            column="39"/>
-    </issue>
-
-    <issue
-        id="AppCompatCustomView"
-        message="This custom view should extend `android.support.v7.widget.AppCompatTextView` instead"
-        errorLine1="public class AppCompatTextView extends TextView implements TintableBackgroundView,"
-        errorLine2="                                       ~~~~~~~~">
-        <location
-            file="src/android/support/v7/widget/AppCompatTextView.java"
-            line="60"
-            column="40"/>
-    </issue>
-
-    <issue
-        id="AppCompatCustomView"
-        message="This custom view should extend `android.support.v7.widget.AppCompatImageView` instead"
-        errorLine1="class CircleImageView extends ImageView {"
-        errorLine2="                              ~~~~~~~~~">
-        <location
-            file="java/android/support/v4/widget/CircleImageView.java"
-            line="38"
-            column="31"/>
-    </issue>
-
-    <issue
-        id="AppCompatCustomView"
-        message="This custom view should extend `android.support.v7.widget.AppCompatTextView` instead"
-        errorLine1="public class DialogTitle extends TextView {"
-        errorLine2="                                 ~~~~~~~~">
-        <location
-            file="src/android/support/v7/widget/DialogTitle.java"
-            line="37"
-            column="34"/>
-    </issue>
-
-    <issue
-        id="AppCompatCustomView"
-        message="This custom view should extend `android.support.v7.widget.AppCompatImageView` instead"
-        errorLine1="public class PreferenceImageView extends ImageView {"
-        errorLine2="                                         ~~~~~~~~~">
-        <location
-            file="src/android/support/v7/internal/widget/PreferenceImageView.java"
-            line="33"
-            column="42"/>
-    </issue>
-
-    <issue
-        id="UniqueConstants"
-        message="Constants `FLAG_CVE_EQ_PVE` and `FLAG_CVE_EQ_PVE` specify the same exact value (8192); this is usually a cut &amp; paste or merge error"
-        errorLine1="            FLAG_CVE_EQ_PVE, FLAG_CVE_EQ_PVE, FLAG_CVE_LT_PVE"
-        errorLine2="                             ~~~~~~~~~~~~~~~">
-        <location
-            file="src/android/support/v7/widget/ViewBoundsCheck.java"
-            line="125"
-            column="30"/>
-        <location
-            file="src/android/support/v7/widget/ViewBoundsCheck.java"
-            line="125"
-            column="13"/>
-    </issue>
-
-    <issue
         id="WrongConstant"
         message="Must be one of: PixelFormat.UNKNOWN, PixelFormat.TRANSLUCENT, PixelFormat.TRANSPARENT, PixelFormat.OPAQUE"
         errorLine1="        return 0;"
@@ -682,35 +227,13 @@
 
     <issue
         id="WrongConstant"
-        message="Must be one of: Gravity.LEFT, Gravity.RIGHT, GravityCompat.START, GravityCompat.END"
-        errorLine1="        setDrawerLockMode(lockMode, gravity);"
-        errorLine2="                                    ~~~~~~~">
+        message="Must be one or more of: Gravity.LEFT, Gravity.RIGHT, GravityCompat.START, GravityCompat.END"
+        errorLine1="                            + gravityToString(childGravity) + &quot; but this &quot; + TAG + &quot; already has a &quot;"
+        errorLine2="                                              ~~~~~~~~~~~~">
         <location
             file="java/android/support/v4/widget/DrawerLayout.java"
-            line="680"
-            column="37"/>
-    </issue>
-
-    <issue
-        id="WrongConstant"
-        message="Must be one of: Gravity.LEFT, Gravity.RIGHT, GravityCompat.START, GravityCompat.END"
-        errorLine1="        return getDrawerLockMode(drawerGravity);"
-        errorLine2="                                 ~~~~~~~~~~~~~">
-        <location
-            file="java/android/support/v4/widget/DrawerLayout.java"
-            line="753"
-            column="34"/>
-    </issue>
-
-    <issue
-        id="WrongConstant"
-        message="Flag not allowed here"
-        errorLine1="                        getDrawerViewAbsoluteGravity(child) &amp; Gravity.HORIZONTAL_GRAVITY_MASK;"
-        errorLine2="                        ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
-        <location
-            file="java/android/support/v4/widget/DrawerLayout.java"
-            line="1086"
-            column="25"/>
+            line="1075"
+            column="47"/>
     </issue>
 
     <issue
@@ -735,15 +258,4 @@
             column="36"/>
     </issue>
 
-    <issue
-        id="WrongConstant"
-        message="Must be one of: View.LAYOUT_DIRECTION_LTR, View.LAYOUT_DIRECTION_RTL"
-        errorLine1="            return isAutoMirrored() &amp;&amp; getLayoutDirection() == LayoutDirection.RTL;"
-        errorLine2="                                                               ~~~~~~~~~~~~~~~~~~~">
-        <location
-            file="src/android/support/graphics/drawable/VectorDrawableCompat.java"
-            line="821"
-            column="64"/>
-    </issue>
-
 </issues>
diff --git a/v7/preference/src/android/support/v7/preference/Preference.java b/v7/preference/src/android/support/v7/preference/Preference.java
index 6b302fd..cfc4311 100644
--- a/v7/preference/src/android/support/v7/preference/Preference.java
+++ b/v7/preference/src/android/support/v7/preference/Preference.java
@@ -999,6 +999,7 @@
      * @attr ref R.styleable#Preference_android_singleLineTitle
      */
     public void setSingleLineTitle(boolean singleLineTitle) {
+        mHasSingleLineTitleAttr = true;
         mSingleLineTitle = singleLineTitle;
     }
 
diff --git a/v7/preference/tests/src/android/support/v7/preference/tests/PreferenceSingleLineTitleTest.java b/v7/preference/tests/src/android/support/v7/preference/tests/PreferenceSingleLineTitleTest.java
new file mode 100644
index 0000000..6c1cf5f
--- /dev/null
+++ b/v7/preference/tests/src/android/support/v7/preference/tests/PreferenceSingleLineTitleTest.java
@@ -0,0 +1,84 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.support.v7.preference.tests;
+
+import static org.mockito.ArgumentMatchers.anyBoolean;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import android.support.test.InstrumentationRegistry;
+import android.support.test.filters.SmallTest;
+import android.support.test.runner.AndroidJUnit4;
+import android.support.v7.preference.Preference;
+import android.support.v7.preference.PreferenceViewHolder;
+import android.view.ViewGroup;
+import android.widget.TextView;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+@SmallTest
+@RunWith(AndroidJUnit4.class)
+public class PreferenceSingleLineTitleTest {
+
+    private Preference mPreference;
+
+    @Mock
+    private ViewGroup mViewGroup;
+    @Mock
+    private TextView mTitleView;
+
+    @Before
+    public void setUp() {
+        MockitoAnnotations.initMocks(this);
+        when(mViewGroup.findViewById(android.R.id.title)).thenReturn(mTitleView);
+
+        mPreference = new Preference(InstrumentationRegistry.getTargetContext());
+        mPreference.setTitle("Test Title");
+    }
+
+    @Test
+    public void bindViewHolder_singleLineTitleNotSet_shouldNotSetSingleLine() {
+        PreferenceViewHolder holder = PreferenceViewHolder.createInstanceForTests(mViewGroup);
+        mPreference.onBindViewHolder(holder);
+
+        verify(mTitleView, never()).setSingleLine(anyBoolean());
+    }
+
+    @Test
+    public void bindViewHolder_singleLineTitleSetToTrue_shouldSetSingleLineToTrue() {
+        PreferenceViewHolder holder = PreferenceViewHolder.createInstanceForTests(mViewGroup);
+        mPreference.setSingleLineTitle(true);
+        mPreference.onBindViewHolder(holder);
+
+        verify(mTitleView).setSingleLine(true);
+    }
+
+    @Test
+    public void bindViewHolder_singleLineTitleSetToFalse_shouldSetSingleLineToFalse() {
+        PreferenceViewHolder holder = PreferenceViewHolder.createInstanceForTests(mViewGroup);
+        mPreference.setSingleLineTitle(false);
+        mPreference.onBindViewHolder(holder);
+
+        verify(mTitleView).setSingleLine(false);
+    }
+
+}
diff --git a/v7/recyclerview/lint-baseline.xml b/v7/recyclerview/lint-baseline.xml
index 443b10a..b373b5e 100644
--- a/v7/recyclerview/lint-baseline.xml
+++ b/v7/recyclerview/lint-baseline.xml
@@ -1,217 +1,15 @@
 <?xml version="1.0" encoding="UTF-8"?>
-<issues format="4" by="lint 2.4.0-alpha6">
-
-    <issue
-        id="Range"
-        message="Value must be ≥ 0 (was -1)"
-        errorLine1="                childHeightSpec = MeasureSpec.makeMeasureSpec(maxLayoutHeight, MeasureSpec.AT_MOST);"
-        errorLine2="                                                              ~~~~~~~~~~~~~~~">
-        <location
-            file="java/android/support/v4/widget/SlidingPaneLayout.java"
-            line="536"
-            column="63"/>
-    </issue>
-
-    <issue
-        id="Range"
-        message="Value must be ≥ 0 (was -1)"
-        errorLine1="                childHeightSpec = MeasureSpec.makeMeasureSpec(maxLayoutHeight, MeasureSpec.EXACTLY);"
-        errorLine2="                                                              ~~~~~~~~~~~~~~~">
-        <location
-            file="java/android/support/v4/widget/SlidingPaneLayout.java"
-            line="538"
-            column="63"/>
-    </issue>
-
-    <issue
-        id="Range"
-        message="Value must be ≥ 0 (was -1)"
-        errorLine1="                                childHeightSpec = MeasureSpec.makeMeasureSpec(maxLayoutHeight,"
-        errorLine2="                                                                              ~~~~~~~~~~~~~~~">
-        <location
-            file="java/android/support/v4/widget/SlidingPaneLayout.java"
-            line="586"
-            column="79"/>
-    </issue>
-
-    <issue
-        id="Range"
-        message="Value must be ≥ 0 (was -1)"
-        errorLine1="                                childHeightSpec = MeasureSpec.makeMeasureSpec(maxLayoutHeight,"
-        errorLine2="                                                                              ~~~~~~~~~~~~~~~">
-        <location
-            file="java/android/support/v4/widget/SlidingPaneLayout.java"
-            line="589"
-            column="79"/>
-    </issue>
-
-    <issue
-        id="Range"
-        message="Value must be ≥ 0 (was -1)"
-        errorLine1="                            childHeightSpec = MeasureSpec.makeMeasureSpec(maxLayoutHeight,"
-        errorLine2="                                                                          ~~~~~~~~~~~~~~~">
-        <location
-            file="java/android/support/v4/widget/SlidingPaneLayout.java"
-            line="608"
-            column="75"/>
-    </issue>
-
-    <issue
-        id="Range"
-        message="Value must be ≥ 0 (was -1)"
-        errorLine1="                            childHeightSpec = MeasureSpec.makeMeasureSpec(maxLayoutHeight,"
-        errorLine2="                                                                          ~~~~~~~~~~~~~~~">
-        <location
-            file="java/android/support/v4/widget/SlidingPaneLayout.java"
-            line="611"
-            column="75"/>
-    </issue>
-
-    <issue
-        id="LongLogTag"
-        message="The logging tag can be at most 23 characters, was 26 (StaggeredGridLayoutManager)"
-        errorLine1="            Log.d(TAG, &quot;found saved state: &quot; + mPendingSavedState);"
-        errorLine2="                  ~~~">
-        <location
-            file="src/android/support/v7/widget/StaggeredGridLayoutManager.java"
-            line="783"
-            column="19"/>
-    </issue>
-
-    <issue
-        id="LongLogTag"
-        message="The logging tag can be at most 23 characters, was 26 (StaggeredGridLayoutManager)"
-        errorLine1="            Log.d(TAG, &quot;Deciding anchor info from fresh state&quot;);"
-        errorLine2="                  ~~~">
-        <location
-            file="src/android/support/v7/widget/StaggeredGridLayoutManager.java"
-            line="828"
-            column="19"/>
-    </issue>
-
-    <issue
-        id="LongLogTag"
-        message="The logging tag can be at most 23 characters, was 26 (StaggeredGridLayoutManager)"
-        errorLine1="            Log.d(TAG, &quot;invalid saved state class&quot;);"
-        errorLine2="                  ~~~">
-        <location
-            file="src/android/support/v7/widget/StaggeredGridLayoutManager.java"
-            line="1187"
-            column="19"/>
-    </issue>
-
-    <issue
-        id="LongLogTag"
-        message="The logging tag can be at most 23 characters, was 26 (StaggeredGridLayoutManager)"
-        errorLine1="            Log.d(TAG, &quot;saved state:\n&quot; + state);"
-        errorLine2="                  ~~~">
-        <location
-            file="src/android/support/v7/widget/StaggeredGridLayoutManager.java"
-            line="1236"
-            column="19"/>
-    </issue>
-
-    <issue
-        id="LongLogTag"
-        message="The logging tag can be at most 23 characters, was 26 (StaggeredGridLayoutManager)"
-        errorLine1="            Log.d(TAG, &quot;FILLING targetLine: &quot; + targetLine + &quot;,&quot;"
-        errorLine2="                  ~~~">
-        <location
-            file="src/android/support/v7/widget/StaggeredGridLayoutManager.java"
-            line="1559"
-            column="19"/>
-    </issue>
-
-    <issue
-        id="LongLogTag"
-        message="The logging tag can be at most 23 characters, was 26 (StaggeredGridLayoutManager)"
-        errorLine1="                    Log.d(TAG, &quot;assigned &quot; + currentSpan.mIndex + &quot; for &quot; + position);"
-        errorLine2="                          ~~~">
-        <location
-            file="src/android/support/v7/widget/StaggeredGridLayoutManager.java"
-            line="1580"
-            column="27"/>
-    </issue>
-
-    <issue
-        id="LongLogTag"
-        message="The logging tag can be at most 23 characters, was 26 (StaggeredGridLayoutManager)"
-        errorLine1="                    Log.d(TAG, &quot;using &quot; + spanIndex + &quot; for pos &quot; + position);"
-        errorLine2="                          ~~~">
-        <location
-            file="src/android/support/v7/widget/StaggeredGridLayoutManager.java"
-            line="1584"
-            column="27"/>
-    </issue>
-
-    <issue
-        id="LongLogTag"
-        message="The logging tag can be at most 23 characters, was 26 (StaggeredGridLayoutManager)"
-        errorLine1="            Log.d(TAG, &quot;asked &quot; + dt + &quot; scrolled&quot; + totalScroll);"
-        errorLine2="                  ~~~">
-        <location
-            file="src/android/support/v7/widget/StaggeredGridLayoutManager.java"
-            line="2153"
-            column="19"/>
-    </issue>
-
-    <issue
-        id="LongLogTag"
-        message="The logging tag can be at most 23 characters, was 26 (StaggeredGridLayoutManager)"
-        errorLine1="                    Log.d(TAG, &quot;Unknown focus request:&quot; + focusDirection);"
-        errorLine2="                          ~~~">
-        <location
-            file="src/android/support/v7/widget/StaggeredGridLayoutManager.java"
-            line="2385"
-            column="27"/>
-    </issue>
-
-    <issue
-        id="UniqueConstants"
-        message="Constants `FLAG_CVE_EQ_PVE` and `FLAG_CVE_EQ_PVE` specify the same exact value (8192); this is usually a cut &amp; paste or merge error"
-        errorLine1="            FLAG_CVE_EQ_PVE, FLAG_CVE_EQ_PVE, FLAG_CVE_LT_PVE"
-        errorLine2="                             ~~~~~~~~~~~~~~~">
-        <location
-            file="src/android/support/v7/widget/ViewBoundsCheck.java"
-            line="125"
-            column="30"/>
-        <location
-            file="src/android/support/v7/widget/ViewBoundsCheck.java"
-            line="125"
-            column="13"/>
-    </issue>
+<issues format="4" by="lint 3.0.0-alpha5">
 
     <issue
         id="WrongConstant"
-        message="Must be one of: Gravity.LEFT, Gravity.RIGHT, GravityCompat.START, GravityCompat.END"
-        errorLine1="        setDrawerLockMode(lockMode, gravity);"
-        errorLine2="                                    ~~~~~~~">
+        message="Must be one or more of: Gravity.LEFT, Gravity.RIGHT, GravityCompat.START, GravityCompat.END"
+        errorLine1="                            + gravityToString(childGravity) + &quot; but this &quot; + TAG + &quot; already has a &quot;"
+        errorLine2="                                              ~~~~~~~~~~~~">
         <location
             file="java/android/support/v4/widget/DrawerLayout.java"
-            line="680"
-            column="37"/>
-    </issue>
-
-    <issue
-        id="WrongConstant"
-        message="Must be one of: Gravity.LEFT, Gravity.RIGHT, GravityCompat.START, GravityCompat.END"
-        errorLine1="        return getDrawerLockMode(drawerGravity);"
-        errorLine2="                                 ~~~~~~~~~~~~~">
-        <location
-            file="java/android/support/v4/widget/DrawerLayout.java"
-            line="753"
-            column="34"/>
-    </issue>
-
-    <issue
-        id="WrongConstant"
-        message="Flag not allowed here"
-        errorLine1="                        getDrawerViewAbsoluteGravity(child) &amp; Gravity.HORIZONTAL_GRAVITY_MASK;"
-        errorLine2="                        ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
-        <location
-            file="java/android/support/v4/widget/DrawerLayout.java"
-            line="1086"
-            column="25"/>
+            line="1075"
+            column="47"/>
     </issue>
 
 </issues>
diff --git a/v7/recyclerview/src/android/support/v7/widget/LinearLayoutManager.java b/v7/recyclerview/src/android/support/v7/widget/LinearLayoutManager.java
index a72975d..a12e9a8 100644
--- a/v7/recyclerview/src/android/support/v7/widget/LinearLayoutManager.java
+++ b/v7/recyclerview/src/android/support/v7/widget/LinearLayoutManager.java
@@ -491,6 +491,7 @@
         // resolve layout direction
         resolveShouldLayoutReverse();
 
+        final View focused = getFocusedChild();
         if (!mAnchorInfo.mValid || mPendingScrollPosition != NO_POSITION
                 || mPendingSavedState != null) {
             mAnchorInfo.reset();
@@ -498,6 +499,22 @@
             // calculate anchor position and coordinate
             updateAnchorInfoForLayout(recycler, state, mAnchorInfo);
             mAnchorInfo.mValid = true;
+        } else if (focused != null && (mOrientationHelper.getDecoratedStart(focused)
+                        >= mOrientationHelper.getEndAfterPadding()
+                || mOrientationHelper.getDecoratedEnd(focused)
+                <= mOrientationHelper.getStartAfterPadding())) {
+            // This case relates to when the anchor child is the focused view and due to layout
+            // shrinking the focused view fell outside the viewport, e.g. when soft keyboard shows
+            // up after tapping an EditText which shrinks RV causing the focused view (The tapped
+            // EditText which is the anchor child) to get kicked out of the screen. Will update the
+            // anchor coordinate in order to make sure that the focused view is laid out. Otherwise,
+            // the available space in layoutState will be calculated as negative preventing the
+            // focused view from being laid out in fill.
+            // Note that we won't update the anchor position between layout passes (refer to
+            // TestResizingRelayoutWithAutoMeasure), which happens if we were to call
+            // updateAnchorInfoForLayout for an anchor that's not the focused view (e.g. a reference
+            // child which can change between layout passes).
+            mAnchorInfo.assignFromViewAndKeepVisibleRect(focused);
         }
         if (DEBUG) {
             Log.d(TAG, "Anchor info:" + mAnchorInfo);
@@ -1293,6 +1310,7 @@
             return;
         }
 
+        ensureLayoutState();
         final int layoutDirection = delta > 0 ? LayoutState.LAYOUT_END : LayoutState.LAYOUT_START;
         final int absDy = Math.abs(delta);
         updateLayoutState(layoutDirection, absDy, true, state);
diff --git a/v7/recyclerview/src/android/support/v7/widget/RecyclerView.java b/v7/recyclerview/src/android/support/v7/widget/RecyclerView.java
index 8033400..33c706f 100644
--- a/v7/recyclerview/src/android/support/v7/widget/RecyclerView.java
+++ b/v7/recyclerview/src/android/support/v7/widget/RecyclerView.java
@@ -3144,6 +3144,14 @@
                 }
                 mAdapterUpdateDuringMeasure = false;
                 resumeRequestLayout(false);
+            } else if (mState.mRunPredictiveAnimations) {
+                // If mAdapterUpdateDuringMeasure is false and mRunPredictiveAnimations is true:
+                // this means there is already an onMeasure() call performed to handle the pending
+                // adapter change, two onMeasure() calls can happen if RV is a child of LinearLayout
+                // with layout_width=MATCH_PARENT. RV cannot call LM.onMeasure() second time
+                // because getViewForPosition() will crash when LM uses a child to measure.
+                defaultOnMeasure(widthSpec, heightSpec);
+                return;
             }
 
             if (mAdapter != null) {
diff --git a/v7/recyclerview/src/android/support/v7/widget/SnapHelper.java b/v7/recyclerview/src/android/support/v7/widget/SnapHelper.java
index d63045b..d85a27a 100644
--- a/v7/recyclerview/src/android/support/v7/widget/SnapHelper.java
+++ b/v7/recyclerview/src/android/support/v7/widget/SnapHelper.java
@@ -19,6 +19,7 @@
 import android.support.annotation.NonNull;
 import android.support.annotation.Nullable;
 import android.support.v7.widget.RecyclerView.LayoutManager;
+import android.support.v7.widget.RecyclerView.SmoothScroller;
 import android.support.v7.widget.RecyclerView.SmoothScroller.ScrollVectorProvider;
 import android.util.DisplayMetrics;
 import android.view.View;
@@ -159,7 +160,7 @@
             return false;
         }
 
-        RecyclerView.SmoothScroller smoothScroller = createSnapScroller(layoutManager);
+        SmoothScroller smoothScroller = createScroller(layoutManager);
         if (smoothScroller == null) {
             return false;
         }
@@ -203,9 +204,24 @@
      * @param layoutManager     The {@link RecyclerView.LayoutManager} associated with the attached
      *                          {@link RecyclerView}.
      *
-     * @return a {@link LinearSmoothScroller} which will handle the scrolling.
+     * @return a {@link SmoothScroller} which will handle the scrolling.
      */
     @Nullable
+    protected SmoothScroller createScroller(LayoutManager layoutManager) {
+        return createSnapScroller(layoutManager);
+    }
+
+    /**
+     * Creates a scroller to be used in the snapping implementation.
+     *
+     * @param layoutManager     The {@link RecyclerView.LayoutManager} associated with the attached
+     *                          {@link RecyclerView}.
+     *
+     * @return a {@link LinearSmoothScroller} which will handle the scrolling.
+     * @deprecated use {@link #createScroller(LayoutManager)} instead.
+     */
+    @Nullable
+    @Deprecated
     protected LinearSmoothScroller createSnapScroller(LayoutManager layoutManager) {
         if (!(layoutManager instanceof ScrollVectorProvider)) {
             return null;
@@ -281,4 +297,4 @@
      */
     public abstract int findTargetSnapPosition(LayoutManager layoutManager, int velocityX,
             int velocityY);
-}
\ No newline at end of file
+}
diff --git a/v7/recyclerview/src/android/support/v7/widget/StaggeredGridLayoutManager.java b/v7/recyclerview/src/android/support/v7/widget/StaggeredGridLayoutManager.java
index 679f483..f3ea045 100644
--- a/v7/recyclerview/src/android/support/v7/widget/StaggeredGridLayoutManager.java
+++ b/v7/recyclerview/src/android/support/v7/widget/StaggeredGridLayoutManager.java
@@ -55,7 +55,7 @@
 public class StaggeredGridLayoutManager extends RecyclerView.LayoutManager implements
         RecyclerView.SmoothScroller.ScrollVectorProvider {
 
-    private static final String TAG = "StaggeredGridLayoutManager";
+    private static final String TAG = "StaggeredGridLManager";
 
     static final boolean DEBUG = false;
 
diff --git a/v7/recyclerview/src/android/support/v7/widget/ViewBoundsCheck.java b/v7/recyclerview/src/android/support/v7/widget/ViewBoundsCheck.java
index 8a4f89c..191a069 100644
--- a/v7/recyclerview/src/android/support/v7/widget/ViewBoundsCheck.java
+++ b/v7/recyclerview/src/android/support/v7/widget/ViewBoundsCheck.java
@@ -122,7 +122,7 @@
             FLAG_CVS_GT_PVS, FLAG_CVS_EQ_PVS, FLAG_CVS_LT_PVS,
             FLAG_CVS_GT_PVE, FLAG_CVS_EQ_PVE, FLAG_CVS_LT_PVE,
             FLAG_CVE_GT_PVS, FLAG_CVE_EQ_PVS, FLAG_CVE_LT_PVS,
-            FLAG_CVE_EQ_PVE, FLAG_CVE_EQ_PVE, FLAG_CVE_LT_PVE
+            FLAG_CVE_GT_PVE, FLAG_CVE_EQ_PVE, FLAG_CVE_LT_PVE
     })
     @Retention(RetentionPolicy.SOURCE)
     public @interface ViewBounds {}
diff --git a/v7/recyclerview/tests/src/android/support/v7/util/ImeCleanUpTestRule.java b/v7/recyclerview/tests/src/android/support/v7/util/ImeCleanUpTestRule.java
new file mode 100644
index 0000000..f4caad3
--- /dev/null
+++ b/v7/recyclerview/tests/src/android/support/v7/util/ImeCleanUpTestRule.java
@@ -0,0 +1,88 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.support.v7.util;
+
+import android.graphics.Rect;
+import android.support.testutils.PollingCheck;
+import android.view.View;
+
+import org.junit.rules.TestRule;
+import org.junit.runner.Description;
+import org.junit.runners.model.Statement;
+
+/**
+ * A JUnit rule that ensures that IME is closed after a test is finished (or exception thrown).
+ * A test that triggers IME open/close should call setContainerView with the activity's container
+ * view in order to trigger this cleanup at the end of the test. Otherwise, no cleanup happens.
+ */
+public class ImeCleanUpTestRule implements TestRule {
+
+    private View mContainerView;
+
+    @Override
+    public Statement apply(final Statement base, Description description) {
+        return new Statement() {
+            @Override
+            public void evaluate() throws Throwable {
+                try {
+                    base.evaluate();
+                } finally {
+                    closeImeIfOpen();
+                }
+            }
+        };
+    }
+
+    /**
+     * Sets the container view used to calculate the total screen height and the height available
+     * to the test activity.
+     */
+    public void setContainerView(View containerView) {
+        mContainerView = containerView;
+    }
+
+    private void closeImeIfOpen() {
+        if (mContainerView == null) {
+            return;
+        }
+        // Ensuring that IME is closed after starting each test.
+        final Rect r = new Rect();
+        mContainerView.getWindowVisibleDisplayFrame(r);
+        // This is the entire height of the screen available to both the view and IME
+        final int screenHeight = mContainerView.getRootView().getHeight();
+
+        // r.bottom is the position above IME if it's open or device button.
+        // if IME is shown, r.bottom is smaller than screenHeight.
+        int imeHeight = screenHeight - r.bottom;
+
+        // Picking a threshold to detect when IME is open
+        if (imeHeight > screenHeight * 0.15) {
+            // Soft keyboard is shown, will wait for it to close after running the test. Note that
+            // we don't press back button here as the IME should close by itself when a test
+            // finishes. If the wait isn't done here, the IME can mess up with the layout of the
+            // next test.
+            PollingCheck.waitFor(new PollingCheck.PollingCheckCondition() {
+                @Override
+                public boolean canProceed() {
+                    mContainerView.getWindowVisibleDisplayFrame(r);
+                    int imeHeight = screenHeight - r.bottom;
+                    return imeHeight < screenHeight * 0.15;
+                }
+            });
+        }
+    }
+}
diff --git a/v7/recyclerview/tests/src/android/support/v7/widget/BaseGridLayoutManagerTest.java b/v7/recyclerview/tests/src/android/support/v7/widget/BaseGridLayoutManagerTest.java
index cc6bbe8..13dd1e4 100644
--- a/v7/recyclerview/tests/src/android/support/v7/widget/BaseGridLayoutManagerTest.java
+++ b/v7/recyclerview/tests/src/android/support/v7/widget/BaseGridLayoutManagerTest.java
@@ -325,6 +325,36 @@
         }
     }
 
+    class GridEditTextAdapter extends EditTextAdapter {
+
+        Set<Integer> mFullSpanItems = new HashSet<Integer>();
+        int mSpanPerItem = 1;
+
+        GridEditTextAdapter(int count) {
+            this(count, 1);
+        }
+
+        GridEditTextAdapter(int count, int spanPerItem) {
+            super(count);
+            mSpanPerItem = spanPerItem;
+        }
+
+        void setFullSpan(int... items) {
+            for (int i : items) {
+                mFullSpanItems.add(i);
+            }
+        }
+
+        void assignSpanSizeLookup(final GridLayoutManager glm) {
+            glm.setSpanSizeLookup(new GridLayoutManager.SpanSizeLookup() {
+                @Override
+                public int getSpanSize(int position) {
+                    return mFullSpanItems.contains(position) ? glm.getSpanCount() : mSpanPerItem;
+                }
+            });
+        }
+    }
+
     class GridTestAdapter extends TestAdapter {
 
         Set<Integer> mFullSpanItems = new HashSet<Integer>();
diff --git a/v7/recyclerview/tests/src/android/support/v7/widget/BaseRecyclerViewInstrumentationTest.java b/v7/recyclerview/tests/src/android/support/v7/widget/BaseRecyclerViewInstrumentationTest.java
index 8ca5f4e..90e0536 100644
--- a/v7/recyclerview/tests/src/android/support/v7/widget/BaseRecyclerViewInstrumentationTest.java
+++ b/v7/recyclerview/tests/src/android/support/v7/widget/BaseRecyclerViewInstrumentationTest.java
@@ -38,10 +38,12 @@
 import android.support.v4.view.ViewCompat;
 import android.support.v7.recyclerview.test.R;
 import android.support.v7.recyclerview.test.SameActivityTestRule;
+import android.text.Editable;
 import android.util.Log;
 import android.view.LayoutInflater;
 import android.view.View;
 import android.view.ViewGroup;
+import android.widget.EditText;
 import android.widget.FrameLayout;
 import android.widget.TextView;
 
@@ -793,6 +795,37 @@
         }
     }
 
+    public class EditTextAdapter extends RecyclerView.Adapter<TestViewHolder> {
+
+        final ArrayList<Editable> mEditables;
+        public EditTextAdapter(int count) {
+            mEditables = new ArrayList<>();
+            for (int i = 0; i < count; ++i) {
+                mEditables.add(Editable.Factory.getInstance().newEditable("Sample Text " + i));
+            }
+        }
+
+        @Override
+        public TestViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
+            final EditText editText = new EditText(parent.getContext());
+            editText.setLayoutParams(new ViewGroup.LayoutParams(
+                    ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT));
+            final TestViewHolder viewHolder = new TestViewHolder(editText);
+            return viewHolder;
+        }
+
+        @Override
+        public void onBindViewHolder(TestViewHolder holder, int position) {
+            ((EditText) holder.itemView).setText(Editable.Factory.getInstance().newEditable(
+                    mEditables.get(position)));
+        }
+
+        @Override
+        public int getItemCount() {
+            return mEditables.size();
+        }
+    }
+
     public class TestAdapter extends RecyclerView.Adapter<TestViewHolder>
             implements AttachDetachCountingAdapter {
 
@@ -1197,6 +1230,27 @@
         }
     }
 
+
+    public static View findFirstFullyVisibleChild(RecyclerView parent) {
+        for (int i = 0; i < parent.getChildCount(); i++) {
+            View child = parent.getChildAt(i);
+            if (isViewFullyInBound(parent, child)) {
+                return child;
+            }
+        }
+        return null;
+    }
+
+    public static View findLastFullyVisibleChild(RecyclerView parent) {
+        for (int i = parent.getChildCount() - 1; i >= 0; i--) {
+            View child = parent.getChildAt(i);
+            if (isViewFullyInBound(parent, child)) {
+                return child;
+            }
+        }
+        return null;
+    }
+
     /**
      * Returns whether a child of RecyclerView is partially in bound. A child is
      * partially in-bounds if it's either fully or partially visible on the screen.
@@ -1232,7 +1286,7 @@
      * @param child The child view to be checked whether is fully within RV's bounds.
      * @return True if the child view is fully visible; false otherwise.
      */
-    public boolean isViewFullyInBound(RecyclerView parent, View child) {
+    public static boolean isViewFullyInBound(RecyclerView parent, View child) {
         if (child == null) {
             return false;
         }
diff --git a/v7/recyclerview/tests/src/android/support/v7/widget/GridLayoutManagerTest.java b/v7/recyclerview/tests/src/android/support/v7/widget/GridLayoutManagerTest.java
index 5ebebfe..23eaf52 100644
--- a/v7/recyclerview/tests/src/android/support/v7/widget/GridLayoutManagerTest.java
+++ b/v7/recyclerview/tests/src/android/support/v7/widget/GridLayoutManagerTest.java
@@ -18,6 +18,9 @@
 
 import static android.support.v7.widget.LinearLayoutManager.HORIZONTAL;
 import static android.support.v7.widget.LinearLayoutManager.VERTICAL;
+import static android.view.ViewGroup.LayoutParams.MATCH_PARENT;
+import static android.view.ViewGroup.LayoutParams.WRAP_CONTENT;
+import static android.view.WindowManager.LayoutParams.SOFT_INPUT_ADJUST_RESIZE;
 
 import static org.hamcrest.CoreMatchers.is;
 import static org.junit.Assert.assertEquals;
@@ -34,15 +37,20 @@
 import android.support.test.filters.LargeTest;
 import android.support.test.filters.SdkSuppress;
 import android.support.test.runner.AndroidJUnit4;
+import android.support.testutils.PollingCheck;
 import android.support.v4.view.AccessibilityDelegateCompat;
 import android.support.v4.view.accessibility.AccessibilityNodeInfoCompat;
+import android.support.v7.util.ImeCleanUpTestRule;
+import android.support.v7.util.TouchUtils;
 import android.test.UiThreadTest;
 import android.util.SparseIntArray;
 import android.util.StateSet;
+import android.view.KeyEvent;
 import android.view.View;
 import android.view.ViewGroup;
 
 import org.hamcrest.CoreMatchers;
+import org.junit.Rule;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 
@@ -56,6 +64,9 @@
 @RunWith(AndroidJUnit4.class)
 public class GridLayoutManagerTest extends BaseGridLayoutManagerTest {
 
+    @Rule
+    public final ImeCleanUpTestRule imeCleanUp = new ImeCleanUpTestRule();
+
     @Test
     public void focusSearchFailureUp() throws Throwable {
         focusSearchFailure(false);
@@ -168,6 +179,101 @@
         }
     }
 
+    @Test
+    public void editTextVisibility() throws Throwable {
+        final int spanCount = 3;
+        final int itemCount = 100;
+
+        imeCleanUp.setContainerView(getActivity().getContainer());
+        RecyclerView recyclerView = new WrappedRecyclerView(getActivity());
+        GridEditTextAdapter editTextAdapter = new GridEditTextAdapter(itemCount) {
+            @Override
+            public TestViewHolder onCreateViewHolder(ViewGroup parent,
+                    int viewType) {
+                TestViewHolder testViewHolder = super.onCreateViewHolder(parent, viewType);
+                // Good to have colors for debugging
+                StateListDrawable stl = new StateListDrawable();
+                stl.addState(new int[]{android.R.attr.state_focused},
+                        new ColorDrawable(Color.RED));
+                stl.addState(StateSet.WILD_CARD, new ColorDrawable(Color.BLUE));
+                //noinspection deprecation using this for kitkat tests
+                testViewHolder.itemView.setBackgroundDrawable(stl);
+                return testViewHolder;
+            }
+        };
+        mActivityRule.runOnUiThread(new Runnable() {
+            @Override
+            public void run() {
+                mActivityRule.getActivity().getWindow().setSoftInputMode(SOFT_INPUT_ADJUST_RESIZE);
+            }
+        });
+
+        recyclerView.setLayoutParams(
+                new ViewGroup.LayoutParams(MATCH_PARENT, WRAP_CONTENT));
+
+        Config config = new Config(spanCount, itemCount);
+        mGlm = new WrappedGridLayoutManager(getActivity(), config.mSpanCount, config.mOrientation,
+                config.mReverseLayout);
+        editTextAdapter.assignSpanSizeLookup(mGlm);
+        recyclerView.setAdapter(editTextAdapter);
+        recyclerView.setLayoutManager(mGlm);
+        waitForFirstLayout(recyclerView);
+
+        // First focus on the last fully visible EditText located at span index #1.
+        View toFocus = findLastFullyVisibleChild(mRecyclerView);
+        int focusIndex = mRecyclerView.getChildAdapterPosition(toFocus);
+        focusIndex = (focusIndex / spanCount) * spanCount + 1;
+        toFocus = mRecyclerView.findViewHolderForAdapterPosition(focusIndex).itemView;
+        assertTrue(focusIndex >= 1 && focusIndex < itemCount);
+
+        final int heightBeforeImeOpen = mRecyclerView.getHeight();
+        TouchUtils.tapView(getInstrumentation(), mRecyclerView, toFocus);
+        getInstrumentation().waitForIdleSync();
+        // Wait for IME to pop up.
+        PollingCheck.waitFor(new PollingCheck.PollingCheckCondition() {
+            @Override
+            public boolean canProceed() {
+                return mRecyclerView.getHeight() < heightBeforeImeOpen;
+            }
+        });
+
+        assertThat("Child at position " + focusIndex + " should be focused",
+                toFocus.hasFocus(), is(true));
+        assertTrue("Child view at adapter pos " + focusIndex + " should be fully visible.",
+                isViewPartiallyInBound(mRecyclerView, toFocus));
+
+        // Close IME
+        final int heightBeforeImeClose = mRecyclerView.getHeight();
+        getInstrumentation().sendKeyDownUpSync(KeyEvent.KEYCODE_BACK);
+        getInstrumentation().waitForIdleSync();
+        // Wait for IME to close
+        PollingCheck.waitFor(new PollingCheck.PollingCheckCondition() {
+            @Override
+            public boolean canProceed() {
+                return mRecyclerView.getHeight() > heightBeforeImeClose;
+            }
+        });
+        assertTrue("Child view at adapter pos " + focusIndex + " should be fully visible.",
+                isViewPartiallyInBound(mRecyclerView, toFocus));
+
+        // Now focus on the first fully visible EditText located at the last span index.
+        toFocus = findFirstFullyVisibleChild(mRecyclerView);
+        focusIndex = mRecyclerView.getChildAdapterPosition(toFocus);
+        focusIndex = (focusIndex / spanCount) * spanCount + (spanCount - 1);
+        toFocus = mRecyclerView.findViewHolderForAdapterPosition(focusIndex).itemView;
+        final int heightBeforeImeOpen2 = mRecyclerView.getHeight();
+        TouchUtils.tapView(getInstrumentation(), mRecyclerView, toFocus);
+        getInstrumentation().waitForIdleSync();
+        // Wait for IME to pop up
+        PollingCheck.waitFor(new PollingCheck.PollingCheckCondition() {
+            @Override
+            public boolean canProceed() {
+                return mRecyclerView.getHeight() < heightBeforeImeOpen2;
+            }
+        });
+        assertTrue("Child view at adapter pos " + focusIndex + " should be fully visible.",
+                isViewPartiallyInBound(mRecyclerView, toFocus));
+    }
 
     @Test
     public void topUnfocusableViewsVisibility() throws Throwable {
diff --git a/v7/recyclerview/tests/src/android/support/v7/widget/LinearLayoutManagerTest.java b/v7/recyclerview/tests/src/android/support/v7/widget/LinearLayoutManagerTest.java
index a2db640..91d0dbf 100644
--- a/v7/recyclerview/tests/src/android/support/v7/widget/LinearLayoutManagerTest.java
+++ b/v7/recyclerview/tests/src/android/support/v7/widget/LinearLayoutManagerTest.java
@@ -18,6 +18,9 @@
 
 import static android.support.v7.widget.LinearLayoutManager.HORIZONTAL;
 import static android.support.v7.widget.LinearLayoutManager.VERTICAL;
+import static android.view.ViewGroup.LayoutParams.MATCH_PARENT;
+import static android.view.ViewGroup.LayoutParams.WRAP_CONTENT;
+import static android.view.WindowManager.LayoutParams.SOFT_INPUT_ADJUST_RESIZE;
 
 import static org.hamcrest.CoreMatchers.is;
 import static org.junit.Assert.assertEquals;
@@ -34,13 +37,19 @@
 import android.os.Build;
 import android.support.test.filters.LargeTest;
 import android.support.test.filters.SdkSuppress;
+import android.support.testutils.PollingCheck;
 import android.support.v4.view.AccessibilityDelegateCompat;
+import android.support.v7.util.ImeCleanUpTestRule;
+import android.support.v7.util.TouchUtils;
 import android.util.Log;
 import android.util.StateSet;
+import android.view.KeyEvent;
 import android.view.View;
 import android.view.ViewGroup;
 import android.view.accessibility.AccessibilityEvent;
+import android.widget.LinearLayout;
 
+import org.junit.Rule;
 import org.junit.Test;
 
 import java.util.ArrayList;
@@ -60,6 +69,111 @@
 @LargeTest
 public class LinearLayoutManagerTest extends BaseLinearLayoutManagerTest {
 
+    @Rule
+    public final ImeCleanUpTestRule imeCleanUp = new ImeCleanUpTestRule();
+
+    @Test
+    public void editTextVisibility() throws Throwable {
+
+        // Simulating a scenario where an EditText is tapped (which will receive focus).
+        // The soft keyboard that's opened overlaps the focused EditText which will shrink RV's
+        // padded bounded area. LLM should still lay out the focused EditText so that it becomes
+        // visible above the soft keyboard.
+        // The condition for this test is setting RV's height to a non-exact height, so that measure
+        // is called twice (once with the larger height and another time with smaller height when
+        // the keyboard shows up). To ensure this resizing of RV, SOFT_INPUT_ADJUST_RESIZE is set.
+        imeCleanUp.setContainerView(getActivity().getContainer());
+        final LinearLayout container = new LinearLayout(getActivity());
+        container.setOrientation(LinearLayout.VERTICAL);
+        container.setLayoutParams(
+                new ViewGroup.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup
+                        .LayoutParams.MATCH_PARENT));
+
+        final EditTextAdapter editTextAdapter = new EditTextAdapter(50);
+
+        mRecyclerView = inflateWrappedRV();
+        ViewGroup.LayoutParams lp = mRecyclerView.getLayoutParams();
+        lp.height = WRAP_CONTENT;
+        lp.width = MATCH_PARENT;
+
+        mRecyclerView.setHasFixedSize(true);
+        mRecyclerView.setAdapter(editTextAdapter);
+        mLayoutManager = new WrappedLinearLayoutManager(getActivity(), VERTICAL, false);
+        mRecyclerView.setLayoutManager(mLayoutManager);
+
+        container.addView(mRecyclerView);
+
+        mLayoutManager.expectLayouts(1);
+        mActivityRule.runOnUiThread(new Runnable() {
+            @Override
+            public void run() {
+                getActivity().getContainer().addView(container);
+            }
+        });
+        mActivityRule.runOnUiThread(new Runnable() {
+            @Override
+            public void run() {
+                mActivityRule.getActivity().getWindow().setSoftInputMode(SOFT_INPUT_ADJUST_RESIZE);
+            }
+        });
+
+        // First focus on the last fully visible EditText.
+        View toFocus = findLastFullyVisibleChild(mRecyclerView);
+        int focusIndex = mRecyclerView.getChildAdapterPosition(toFocus);
+
+        final int heightBeforeImeOpen = mRecyclerView.getHeight();
+        TouchUtils.tapView(getInstrumentation(), mRecyclerView, toFocus);
+        getInstrumentation().waitForIdleSync();
+       // Wait for IME to pop up.
+        PollingCheck.waitFor(new PollingCheck.PollingCheckCondition() {
+            @Override
+            public boolean canProceed() {
+                return mRecyclerView.getHeight() < heightBeforeImeOpen;
+            }
+        });
+
+        assertThat("Child at position " + focusIndex + " should be focused",
+                toFocus.hasFocus(), is(true));
+        // Testing for partial visibility instead of full visibility since TextView calls
+        // requestRectangleOnScreen (inside bringPointIntoView) for the focused view with a rect
+        // containing the content area. This rect is guaranteed to be fully visible whereas a
+        // portion of TextView could be out of bounds.
+        assertTrue("Child view at adapter pos " + focusIndex + " should be fully visible.",
+                isViewPartiallyInBound(mRecyclerView, toFocus));
+
+        // Close IME
+        final int heightBeforeImeClose = mRecyclerView.getHeight();
+        getInstrumentation().sendKeyDownUpSync(KeyEvent.KEYCODE_BACK);
+        getInstrumentation().waitForIdleSync();
+        // Wait for IME to close
+        PollingCheck.waitFor(new PollingCheck.PollingCheckCondition() {
+            @Override
+            public boolean canProceed() {
+                return mRecyclerView.getHeight() > heightBeforeImeClose;
+            }
+        });
+        assertThat("Child at position " + focusIndex + " should be focused",
+                toFocus.hasFocus(), is(true));
+        assertTrue("Child view at adapter pos " + focusIndex + " should be fully visible.",
+                isViewPartiallyInBound(mRecyclerView, toFocus));
+
+        // Now focus on the first fully visible EditText.
+        toFocus = findFirstFullyVisibleChild(mRecyclerView);
+        focusIndex = mRecyclerView.getChildAdapterPosition(toFocus);
+        final int heightBeforeImeOpen2 = mRecyclerView.getHeight();
+        TouchUtils.tapView(getInstrumentation(), mRecyclerView, toFocus);
+        getInstrumentation().waitForIdleSync();
+        // Wait for IME to pop up
+        PollingCheck.waitFor(new PollingCheck.PollingCheckCondition() {
+            @Override
+            public boolean canProceed() {
+                return mRecyclerView.getHeight() < heightBeforeImeOpen2;
+            }
+        });
+        assertTrue("Child view at adapter pos " + focusIndex + " should be fully visible.",
+                isViewPartiallyInBound(mRecyclerView, toFocus));
+    }
+
     @Test
     public void topUnfocusableViewsVisibility() throws Throwable {
         // The maximum number of child views that can be visible at any time.
diff --git a/v7/recyclerview/tests/src/android/support/v7/widget/RecyclerViewCacheTest.java b/v7/recyclerview/tests/src/android/support/v7/widget/RecyclerViewCacheTest.java
index 620953a..1267fdb 100644
--- a/v7/recyclerview/tests/src/android/support/v7/widget/RecyclerViewCacheTest.java
+++ b/v7/recyclerview/tests/src/android/support/v7/widget/RecyclerViewCacheTest.java
@@ -298,6 +298,41 @@
     }
 
     @Test
+    public void prefetchAfterOrientationChange() {
+        LinearLayoutManager layout = new LinearLayoutManager(getContext(),
+                LinearLayoutManager.VERTICAL, false);
+        mRecyclerView.setLayoutManager(layout);
+
+        // 100x100 pixel views
+        mRecyclerView.setAdapter(new RecyclerView.Adapter() {
+            @Override
+            public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
+                View view = new View(getContext());
+                view.setMinimumWidth(100);
+                view.setMinimumHeight(100);
+                assertTrue(mRecyclerView.isComputingLayout());
+                return new RecyclerView.ViewHolder(view) {};
+            }
+
+            @Override
+            public void onBindViewHolder(RecyclerView.ViewHolder holder, int position) {}
+
+            @Override
+            public int getItemCount() {
+                return 100;
+            }
+        });
+
+        layout(100, 100);
+
+        layout.setOrientation(LinearLayoutManager.HORIZONTAL);
+
+        // Prefetch an item after changing orientation, before layout - shouldn't crash
+        mRecyclerView.mPrefetchRegistry.setPrefetchVector(1, 1);
+        mRecyclerView.mGapWorker.prefetch(RecyclerView.FOREVER_NS);
+    }
+
+    @Test
     public void prefetchDrag() {
         // event dispatch requires a parent
         ViewGroup parent = new FrameLayout(getContext());
diff --git a/v7/recyclerview/tests/src/android/support/v7/widget/RecyclerViewLayoutTest.java b/v7/recyclerview/tests/src/android/support/v7/widget/RecyclerViewLayoutTest.java
index be687c3..1547049 100644
--- a/v7/recyclerview/tests/src/android/support/v7/widget/RecyclerViewLayoutTest.java
+++ b/v7/recyclerview/tests/src/android/support/v7/widget/RecyclerViewLayoutTest.java
@@ -252,6 +252,66 @@
     }
 
     @Test
+    public void preditiveMeasuredCrashTest() throws Throwable {
+        final RecyclerView rv = new RecyclerView(getActivity());
+        final LayoutAllLayoutManager lm = new LayoutAllLayoutManager(true) {
+            @Override
+            public void onAttachedToWindow(RecyclerView view) {
+                super.onAttachedToWindow(view);
+                assertThat(view.mLayout, is((RecyclerView.LayoutManager) this));
+            }
+
+            @Override
+            public void onDetachedFromWindow(RecyclerView view, RecyclerView.Recycler recycler) {
+                super.onDetachedFromWindow(view, recycler);
+                assertThat(view.mLayout, is((RecyclerView.LayoutManager) this));
+            }
+
+            @Override
+            public void onMeasure(RecyclerView.Recycler recycler, RecyclerView.State state,
+                    int widthSpec,
+                    int heightSpec) {
+                if (state.getItemCount() > 0) {
+                    // A typical LayoutManager will use a child view to measure the size.
+                    View v = recycler.getViewForPosition(0);
+                }
+                super.onMeasure(recycler, state, widthSpec, heightSpec);
+            }
+        };
+        lm.setSupportsPredictive(true);
+        lm.setAutoMeasureEnabled(false);
+        rv.setHasFixedSize(false);
+        final TestAdapter adapter = new TestAdapter(0);
+        rv.setAdapter(adapter);
+        rv.setLayoutManager(lm);
+        lm.expectLayouts(1);
+        setRecyclerView(rv);
+        lm.waitForLayout(2);
+        mActivityRule.runOnUiThread(new Runnable() {
+            @Override
+            public void run() {
+                ViewGroup parent = (ViewGroup) rv.getParent();
+                parent.removeView(rv);
+                // setting RV as child of LinearLayout using MATCH_PARENT will cause
+                // RV.onMeasure() being called twice before layout(). This may cause crash.
+                LinearLayout linearLayout = new LinearLayout(parent.getContext());
+                linearLayout.setOrientation(LinearLayout.VERTICAL);
+                parent.addView(linearLayout,
+                        ViewGroup.LayoutParams.WRAP_CONTENT,
+                        ViewGroup.LayoutParams.WRAP_CONTENT);
+                linearLayout.addView(rv, ViewGroup.LayoutParams.MATCH_PARENT,
+                        ViewGroup.LayoutParams.WRAP_CONTENT);
+
+            }
+        });
+
+        lm.expectLayouts(1);
+        adapter.addAndNotify(1);
+        lm.waitForLayout(2);
+        checkForMainThreadException();
+    }
+
+    @Test
     public void detachRvAndLayoutManagerProperly() throws Throwable {
         final RecyclerView rv = new RecyclerView(getActivity());
         final LayoutAllLayoutManager lm = new LayoutAllLayoutManager(true) {
diff --git a/wear/lint-baseline.xml b/wear/lint-baseline.xml
index ba39be4..452f016 100644
--- a/wear/lint-baseline.xml
+++ b/wear/lint-baseline.xml
@@ -1,5 +1,5 @@
 <?xml version="1.0" encoding="UTF-8"?>
-<issues format="4" by="lint 2.4.0-alpha6">
+<issues format="4" by="lint 3.0.0-alpha5">
 
     <issue
         id="DuplicateIds"
@@ -28,216 +28,14 @@
     </issue>
 
     <issue
-        id="Range"
-        message="Value must be ≥ 0 (was -1)"
-        errorLine1="                childHeightSpec = MeasureSpec.makeMeasureSpec(maxLayoutHeight, MeasureSpec.AT_MOST);"
-        errorLine2="                                                              ~~~~~~~~~~~~~~~">
-        <location
-            file="java/android/support/v4/widget/SlidingPaneLayout.java"
-            line="536"
-            column="63"/>
-    </issue>
-
-    <issue
-        id="Range"
-        message="Value must be ≥ 0 (was -1)"
-        errorLine1="                childHeightSpec = MeasureSpec.makeMeasureSpec(maxLayoutHeight, MeasureSpec.EXACTLY);"
-        errorLine2="                                                              ~~~~~~~~~~~~~~~">
-        <location
-            file="java/android/support/v4/widget/SlidingPaneLayout.java"
-            line="538"
-            column="63"/>
-    </issue>
-
-    <issue
-        id="Range"
-        message="Value must be ≥ 0 (was -1)"
-        errorLine1="                                childHeightSpec = MeasureSpec.makeMeasureSpec(maxLayoutHeight,"
-        errorLine2="                                                                              ~~~~~~~~~~~~~~~">
-        <location
-            file="java/android/support/v4/widget/SlidingPaneLayout.java"
-            line="586"
-            column="79"/>
-    </issue>
-
-    <issue
-        id="Range"
-        message="Value must be ≥ 0 (was -1)"
-        errorLine1="                                childHeightSpec = MeasureSpec.makeMeasureSpec(maxLayoutHeight,"
-        errorLine2="                                                                              ~~~~~~~~~~~~~~~">
-        <location
-            file="java/android/support/v4/widget/SlidingPaneLayout.java"
-            line="589"
-            column="79"/>
-    </issue>
-
-    <issue
-        id="Range"
-        message="Value must be ≥ 0 (was -1)"
-        errorLine1="                            childHeightSpec = MeasureSpec.makeMeasureSpec(maxLayoutHeight,"
-        errorLine2="                                                                          ~~~~~~~~~~~~~~~">
-        <location
-            file="java/android/support/v4/widget/SlidingPaneLayout.java"
-            line="608"
-            column="75"/>
-    </issue>
-
-    <issue
-        id="Range"
-        message="Value must be ≥ 0 (was -1)"
-        errorLine1="                            childHeightSpec = MeasureSpec.makeMeasureSpec(maxLayoutHeight,"
-        errorLine2="                                                                          ~~~~~~~~~~~~~~~">
-        <location
-            file="java/android/support/v4/widget/SlidingPaneLayout.java"
-            line="611"
-            column="75"/>
-    </issue>
-
-    <issue
-        id="LongLogTag"
-        message="The logging tag can be at most 23 characters, was 26 (StaggeredGridLayoutManager)"
-        errorLine1="            Log.d(TAG, &quot;found saved state: &quot; + mPendingSavedState);"
-        errorLine2="                  ~~~">
-        <location
-            file="src/android/support/v7/widget/StaggeredGridLayoutManager.java"
-            line="783"
-            column="19"/>
-    </issue>
-
-    <issue
-        id="LongLogTag"
-        message="The logging tag can be at most 23 characters, was 26 (StaggeredGridLayoutManager)"
-        errorLine1="            Log.d(TAG, &quot;Deciding anchor info from fresh state&quot;);"
-        errorLine2="                  ~~~">
-        <location
-            file="src/android/support/v7/widget/StaggeredGridLayoutManager.java"
-            line="828"
-            column="19"/>
-    </issue>
-
-    <issue
-        id="LongLogTag"
-        message="The logging tag can be at most 23 characters, was 26 (StaggeredGridLayoutManager)"
-        errorLine1="            Log.d(TAG, &quot;invalid saved state class&quot;);"
-        errorLine2="                  ~~~">
-        <location
-            file="src/android/support/v7/widget/StaggeredGridLayoutManager.java"
-            line="1187"
-            column="19"/>
-    </issue>
-
-    <issue
-        id="LongLogTag"
-        message="The logging tag can be at most 23 characters, was 26 (StaggeredGridLayoutManager)"
-        errorLine1="            Log.d(TAG, &quot;saved state:\n&quot; + state);"
-        errorLine2="                  ~~~">
-        <location
-            file="src/android/support/v7/widget/StaggeredGridLayoutManager.java"
-            line="1236"
-            column="19"/>
-    </issue>
-
-    <issue
-        id="LongLogTag"
-        message="The logging tag can be at most 23 characters, was 26 (StaggeredGridLayoutManager)"
-        errorLine1="            Log.d(TAG, &quot;FILLING targetLine: &quot; + targetLine + &quot;,&quot;"
-        errorLine2="                  ~~~">
-        <location
-            file="src/android/support/v7/widget/StaggeredGridLayoutManager.java"
-            line="1559"
-            column="19"/>
-    </issue>
-
-    <issue
-        id="LongLogTag"
-        message="The logging tag can be at most 23 characters, was 26 (StaggeredGridLayoutManager)"
-        errorLine1="                    Log.d(TAG, &quot;assigned &quot; + currentSpan.mIndex + &quot; for &quot; + position);"
-        errorLine2="                          ~~~">
-        <location
-            file="src/android/support/v7/widget/StaggeredGridLayoutManager.java"
-            line="1580"
-            column="27"/>
-    </issue>
-
-    <issue
-        id="LongLogTag"
-        message="The logging tag can be at most 23 characters, was 26 (StaggeredGridLayoutManager)"
-        errorLine1="                    Log.d(TAG, &quot;using &quot; + spanIndex + &quot; for pos &quot; + position);"
-        errorLine2="                          ~~~">
-        <location
-            file="src/android/support/v7/widget/StaggeredGridLayoutManager.java"
-            line="1584"
-            column="27"/>
-    </issue>
-
-    <issue
-        id="LongLogTag"
-        message="The logging tag can be at most 23 characters, was 26 (StaggeredGridLayoutManager)"
-        errorLine1="            Log.d(TAG, &quot;asked &quot; + dt + &quot; scrolled&quot; + totalScroll);"
-        errorLine2="                  ~~~">
-        <location
-            file="src/android/support/v7/widget/StaggeredGridLayoutManager.java"
-            line="2153"
-            column="19"/>
-    </issue>
-
-    <issue
-        id="LongLogTag"
-        message="The logging tag can be at most 23 characters, was 26 (StaggeredGridLayoutManager)"
-        errorLine1="                    Log.d(TAG, &quot;Unknown focus request:&quot; + focusDirection);"
-        errorLine2="                          ~~~">
-        <location
-            file="src/android/support/v7/widget/StaggeredGridLayoutManager.java"
-            line="2385"
-            column="27"/>
-    </issue>
-
-    <issue
-        id="UniqueConstants"
-        message="Constants `FLAG_CVE_EQ_PVE` and `FLAG_CVE_EQ_PVE` specify the same exact value (8192); this is usually a cut &amp; paste or merge error"
-        errorLine1="            FLAG_CVE_EQ_PVE, FLAG_CVE_EQ_PVE, FLAG_CVE_LT_PVE"
-        errorLine2="                             ~~~~~~~~~~~~~~~">
-        <location
-            file="src/android/support/v7/widget/ViewBoundsCheck.java"
-            line="125"
-            column="30"/>
-        <location
-            file="src/android/support/v7/widget/ViewBoundsCheck.java"
-            line="125"
-            column="13"/>
-    </issue>
-
-    <issue
         id="WrongConstant"
-        message="Must be one of: Gravity.LEFT, Gravity.RIGHT, GravityCompat.START, GravityCompat.END"
-        errorLine1="        setDrawerLockMode(lockMode, gravity);"
-        errorLine2="                                    ~~~~~~~">
+        message="Must be one or more of: Gravity.LEFT, Gravity.RIGHT, GravityCompat.START, GravityCompat.END"
+        errorLine1="                            + gravityToString(childGravity) + &quot; but this &quot; + TAG + &quot; already has a &quot;"
+        errorLine2="                                              ~~~~~~~~~~~~">
         <location
             file="java/android/support/v4/widget/DrawerLayout.java"
-            line="680"
-            column="37"/>
-    </issue>
-
-    <issue
-        id="WrongConstant"
-        message="Must be one of: Gravity.LEFT, Gravity.RIGHT, GravityCompat.START, GravityCompat.END"
-        errorLine1="        return getDrawerLockMode(drawerGravity);"
-        errorLine2="                                 ~~~~~~~~~~~~~">
-        <location
-            file="java/android/support/v4/widget/DrawerLayout.java"
-            line="753"
-            column="34"/>
-    </issue>
-
-    <issue
-        id="WrongConstant"
-        message="Flag not allowed here"
-        errorLine1="                        getDrawerViewAbsoluteGravity(child) &amp; Gravity.HORIZONTAL_GRAVITY_MASK;"
-        errorLine2="                        ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
-        <location
-            file="java/android/support/v4/widget/DrawerLayout.java"
-            line="1086"
-            column="25"/>
+            line="1075"
+            column="47"/>
     </issue>
 
 </issues>
diff --git a/wear/src/android/support/wear/widget/CircularProgressLayout.java b/wear/src/android/support/wear/widget/CircularProgressLayout.java
index 1bfcc39..c317f28 100644
--- a/wear/src/android/support/wear/widget/CircularProgressLayout.java
+++ b/wear/src/android/support/wear/widget/CircularProgressLayout.java
@@ -20,9 +20,13 @@
 import android.content.res.Resources;
 import android.content.res.TypedArray;
 import android.graphics.Color;
+import android.graphics.Paint;
+import android.os.Build;
 import android.support.annotation.ColorInt;
 import android.support.annotation.NonNull;
 import android.support.annotation.Nullable;
+import android.support.annotation.RequiresApi;
+import android.support.v4.content.ContextCompat;
 import android.support.v4.widget.CircularProgressDrawable;
 import android.support.wear.R;
 import android.util.AttributeSet;
@@ -44,12 +48,13 @@
  * <p>Alternatively, this layout can be used to show indeterminate progress by calling {@link
  * #setIndeterminate(boolean)} method.
  */
+@RequiresApi(Build.VERSION_CODES.LOLLIPOP)
 public class CircularProgressLayout extends FrameLayout {
 
     /**
-     * Update interval for 30 fps.
+     * Update interval for 60 fps.
      */
-    private static final long DEFAULT_UPDATE_INTERVAL = 1000 / 30;
+    private static final long DEFAULT_UPDATE_INTERVAL = 1000 / 60;
 
     /**
      * Starting rotation for the progress indicator. Geometric clockwise [0..360] degree range
@@ -110,6 +115,7 @@
 
         mProgressDrawable = new CircularProgressDrawable(context);
         mProgressDrawable.setProgressRotation(DEFAULT_ROTATION);
+        mProgressDrawable.setStrokeCap(Paint.Cap.BUTT);
         setBackground(mProgressDrawable);
 
         // If a child view is added, make it center aligned so it fits in the progress drawable.
@@ -149,7 +155,8 @@
                         R.dimen.circular_progress_layout_stroke_width)));
 
         setBackgroundColor(a.getColor(R.styleable.CircularProgressLayout_backgroundColor,
-                r.getColor(R.color.circular_progress_layout_background_color, null)));
+                ContextCompat.getColor(context,
+                        R.color.circular_progress_layout_background_color)));
 
         setIndeterminate(a.getBoolean(R.styleable.CircularProgressLayout_indeterminate, false));
 
diff --git a/wear/src/android/support/wear/widget/RoundedDrawable.java b/wear/src/android/support/wear/widget/RoundedDrawable.java
new file mode 100644
index 0000000..627e2de
--- /dev/null
+++ b/wear/src/android/support/wear/widget/RoundedDrawable.java
@@ -0,0 +1,252 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package android.support.wear.widget;
+
+import android.annotation.TargetApi;
+import android.graphics.Bitmap;
+import android.graphics.BitmapShader;
+import android.graphics.Canvas;
+import android.graphics.Color;
+import android.graphics.ColorFilter;
+import android.graphics.Paint;
+import android.graphics.PixelFormat;
+import android.graphics.Rect;
+import android.graphics.RectF;
+import android.graphics.Shader;
+import android.graphics.drawable.Drawable;
+import android.os.Build;
+import android.support.annotation.ColorInt;
+import android.support.annotation.NonNull;
+import android.support.annotation.Nullable;
+import android.support.annotation.VisibleForTesting;
+
+import java.util.Objects;
+
+/**
+ * Maintains and draws a drawable inside rounded rectangular bounds.
+ *
+ * The drawable set by the {@link #setDrawable(Drawable)} method will be drawn within the rounded
+ * bounds specified by {@link #setBounds(Rect)} and {@link #setRadius(int)} when the
+ * {@link #draw(Canvas)} method is called.
+ *
+ * By default, RoundedDrawable will apply padding to the drawable inside to fit the drawable into
+ * the rounded rectangle. If clipping is enabled by the {@link #setClipEnabled(boolean)} method, it
+ * will clip the drawable to a rounded rectangle instead of resizing it.
+ *
+ * The {@link #setRadius(int)} method is used to specify the amount of border radius applied to the
+ * corners of inner drawable, regardless of whether or not the clipping is enabled, border radius
+ * will be applied to prevent overflowing of the drawable from specified rounded rectangular area.
+ */
+@TargetApi(Build.VERSION_CODES.N)
+public class RoundedDrawable extends Drawable {
+
+    @VisibleForTesting
+    final Paint mPaint;
+    final Paint mBackgroundPaint;
+
+    @Nullable
+    private Drawable mDrawable;
+    private int mRadius; // Radius applied to corners in pixels
+    private boolean mIsClipEnabled;
+
+    // Used to avoid creating new Rect objects every time draw() is called
+    private final Rect mTmpBounds = new Rect();
+    private final RectF mTmpBoundsF = new RectF();
+
+    public RoundedDrawable() {
+        mPaint = new Paint();
+        mPaint.setAntiAlias(true);
+        mBackgroundPaint = new Paint();
+        mBackgroundPaint.setAntiAlias(true);
+        mBackgroundPaint.setColor(Color.TRANSPARENT);
+    }
+
+    /**
+     * Sets the drawable to be rendered.
+     *
+     * @param drawable {@link Drawable} to be rendered
+     */
+    public void setDrawable(@Nullable Drawable drawable) {
+        if (Objects.equals(mDrawable, drawable)) {
+            return;
+        }
+        mDrawable = drawable;
+        mPaint.setShader(null); // Clear the shader so it can be reinitialized
+        invalidateSelf();
+    }
+
+    /**
+     * Returns the drawable that will be rendered.
+     *
+     * @return {@link Drawable} that will be rendered.
+     */
+    @Nullable
+    public Drawable getDrawable() {
+        return mDrawable;
+    }
+
+    /**
+     * Sets the background color of the rounded drawable.
+     *
+     * @param color an ARGB color
+     */
+    public void setBackgroundColor(@ColorInt int color) {
+        mBackgroundPaint.setColor(color);
+        invalidateSelf();
+    }
+
+    /**
+     * Returns the background color.
+     *
+     * @return an ARGB color
+     */
+    @ColorInt
+    public int getBackgroundColor() {
+        return mBackgroundPaint.getColor();
+    }
+
+    /**
+     * Sets whether the drawable inside should be clipped or resized to fit the rounded bounds. If
+     * the drawable is animated, don't set clipping to {@code true} as clipping on animated
+     * drawables is not supported.
+     *
+     * @param clipEnabled {@code true} if the drawable should be clipped, {@code false} if it
+     *                    should be resized.
+     */
+    public void setClipEnabled(boolean clipEnabled) {
+        mIsClipEnabled = clipEnabled;
+        if (!clipEnabled) {
+            mPaint.setShader(null); // Clear the shader so it's garbage collected
+        }
+        invalidateSelf();
+    }
+
+    /**
+     * Returns whether the drawable inside is clipped or resized to fit the rounded bounds.
+     *
+     * @return {@code true} if the drawable is clipped, {@code false} if it's resized.
+     */
+    public boolean isClipEnabled() {
+        return mIsClipEnabled;
+    }
+
+    @Override
+    protected void onBoundsChange(Rect bounds) {
+        mTmpBounds.right = bounds.width();
+        mTmpBounds.bottom = bounds.height();
+        mTmpBoundsF.right = bounds.width();
+        mTmpBoundsF.bottom = bounds.height();
+        mPaint.setShader(null); // Clear the shader so it can be reinitialized
+    }
+
+    @Override
+    public void draw(@NonNull Canvas canvas) {
+        Rect bounds = getBounds();
+        if (mDrawable == null || bounds.isEmpty()) {
+            return;
+        }
+        canvas.save();
+        canvas.translate(bounds.left, bounds.top);
+        // mTmpBoundsF is bounds translated to (0,0) and converted to RectF as drawRoundRect
+        // requires.
+        canvas.drawRoundRect(mTmpBoundsF, (float) mRadius, (float) mRadius,
+                mBackgroundPaint);
+        if (mIsClipEnabled) {
+            // Update the shader if it's not present.
+            if (mPaint.getShader() == null) {
+                updateBitmapShader();
+            }
+            // Clip to a rounded rectangle
+            canvas.drawRoundRect(mTmpBoundsF, (float) mRadius, (float) mRadius, mPaint);
+        } else {
+            // Scale to fit the rounded rectangle
+            int minEdge = Math.min(bounds.width(), bounds.height());
+            int padding = (int) Math.ceil(
+                    Math.min(mRadius, minEdge / 2) * (1 - 1 / (float) Math.sqrt(2.0)));
+            mTmpBounds.inset(padding, padding);
+            mDrawable.setBounds(mTmpBounds);
+            mDrawable.draw(canvas);
+            mTmpBounds.inset(-padding, -padding);
+        }
+        canvas.restore();
+    }
+
+    @Override
+    public int getOpacity() {
+        return PixelFormat.TRANSLUCENT;
+    }
+
+    @Override
+    public void setAlpha(int alpha) {
+        mPaint.setAlpha(alpha);
+        mBackgroundPaint.setAlpha(alpha);
+    }
+
+    @Override
+    public int getAlpha() {
+        return mPaint.getAlpha();
+    }
+
+    @Override
+    public void setColorFilter(ColorFilter cf) {
+        mPaint.setColorFilter(cf);
+    }
+
+    /**
+     * Sets the border radius to be applied when rendering the drawable in pixels.
+     *
+     * @param radius radius in pixels
+     */
+    public void setRadius(int radius) {
+        mRadius = radius;
+    }
+
+    /**
+     * Returns the border radius applied when rendering the drawable in pixels.
+     *
+     * @return radius in pixels
+     */
+    public int getRadius() {
+        return mRadius;
+    }
+
+    /**
+     * Updates the shader of the paint. To avoid scaling and creation of a BitmapShader every time,
+     * this method should be called only if the drawable or the bounds has changed.
+     */
+    private void updateBitmapShader() {
+        if (mDrawable == null) {
+            return;
+        }
+        Rect bounds = getBounds();
+        if (!bounds.isEmpty()) {
+            Bitmap bitmap = drawableToBitmap(mDrawable, bounds.width(), bounds.height());
+
+            Shader shader = new BitmapShader(bitmap, Shader.TileMode.CLAMP, Shader.TileMode.CLAMP);
+            mPaint.setShader(shader);
+        }
+    }
+
+    /** Converts a drawable to a bitmap of specified width and height. */
+    private Bitmap drawableToBitmap(Drawable drawable, int width, int height) {
+        Bitmap bitmap = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888);
+
+        Canvas canvas = new Canvas(bitmap);
+        drawable.setBounds(0, 0, width, height);
+        drawable.draw(canvas);
+        return bitmap;
+    }
+}
diff --git a/wear/tests/res/layout/rounded_drawable_layout.xml b/wear/tests/res/layout/rounded_drawable_layout.xml
new file mode 100644
index 0000000..6c83b58
--- /dev/null
+++ b/wear/tests/res/layout/rounded_drawable_layout.xml
@@ -0,0 +1,23 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  ~ Copyright (C) 2017 The Android Open Source Project
+  ~
+  ~ Licensed under the Apache License, Version 2.0 (the "License");
+  ~ you may not use this file except in compliance with the License.
+  ~ You may obtain a copy of the License at
+  ~
+  ~      http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing, software
+  ~ distributed under the License is distributed on an "AS IS" BASIS,
+  ~ WITHOUT 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>
\ No newline at end of file
diff --git a/wear/tests/src/android/support/wear/widget/RoundedDrawableTest.java b/wear/tests/src/android/support/wear/widget/RoundedDrawableTest.java
new file mode 100644
index 0000000..dbe575f
--- /dev/null
+++ b/wear/tests/src/android/support/wear/widget/RoundedDrawableTest.java
@@ -0,0 +1,127 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package android.support.wear.widget;
+
+import static org.junit.Assert.assertEquals;
+import static org.mockito.Matchers.any;
+import static org.mockito.Matchers.eq;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
+
+import android.content.Intent;
+import android.graphics.Bitmap;
+import android.graphics.Canvas;
+import android.graphics.ColorFilter;
+import android.graphics.Paint;
+import android.graphics.Rect;
+import android.graphics.RectF;
+import android.graphics.drawable.BitmapDrawable;
+import android.support.test.filters.SmallTest;
+import android.support.test.rule.ActivityTestRule;
+import android.support.test.runner.AndroidJUnit4;
+
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+
+/** Tests for {@link RoundedDrawable} */
+@SmallTest
+@RunWith(AndroidJUnit4.class)
+public class RoundedDrawableTest {
+
+    @Rule
+    public final ActivityTestRule<LayoutTestActivity> mActivityRule = new ActivityTestRule<>(
+            LayoutTestActivity.class, true, false);
+    private static final int BITMAP_WIDTH = 64;
+    private static final int BITMAP_HEIGHT = 32;
+
+    private RoundedDrawable mRoundedDrawable;
+    private BitmapDrawable mBitmapDrawable;
+
+    @Mock
+    Canvas mMockCanvas;
+
+    @Before
+    public void setUp() {
+        mMockCanvas = mock(Canvas.class);
+        mActivityRule.launchActivity(new Intent().putExtra(LayoutTestActivity
+                        .EXTRA_LAYOUT_RESOURCE_ID,
+                android.support.wear.test.R.layout.rounded_drawable_layout));
+        mRoundedDrawable = new RoundedDrawable();
+        mBitmapDrawable =
+                new BitmapDrawable(
+                        mActivityRule.getActivity().getResources(),
+                        Bitmap.createBitmap(BITMAP_WIDTH, BITMAP_HEIGHT, Bitmap.Config.ARGB_8888));
+    }
+
+    @Test
+    public void colorFilterIsAppliedCorrectly() {
+        ColorFilter cf = new ColorFilter();
+        mRoundedDrawable.setColorFilter(cf);
+        assertEquals(cf, mRoundedDrawable.mPaint.getColorFilter());
+    }
+
+    @Test
+    public void alphaIsAppliedCorrectly() {
+        int alpha = 128;
+        mRoundedDrawable.setAlpha(alpha);
+        assertEquals(alpha, mRoundedDrawable.mPaint.getAlpha());
+    }
+
+    @Test
+    public void radiusIsAppliedCorrectly() {
+        int radius = 10;
+        Rect bounds = new Rect(0, 0, BITMAP_WIDTH, BITMAP_HEIGHT);
+        mRoundedDrawable.setDrawable(mBitmapDrawable);
+        mRoundedDrawable.setClipEnabled(true);
+        mRoundedDrawable.setRadius(radius);
+        mRoundedDrawable.setBounds(bounds);
+        mRoundedDrawable.draw(mMockCanvas);
+        // One for background and one for the actual drawable, this should be called two times.
+        verify(mMockCanvas, times(2))
+                .drawRoundRect(
+                        eq(new RectF(0, 0, bounds.width(), bounds.height())),
+                        eq((float) radius),
+                        eq((float) radius),
+                        any(Paint.class));
+    }
+
+    @Test
+    public void scalingIsAppliedCorrectly() {
+        int radius = 14;
+        // 14 px radius should apply 5 px padding due to formula ceil(radius * 1 - 1 / sqrt(2))
+        Rect bounds = new Rect(0, 0, BITMAP_WIDTH, BITMAP_HEIGHT);
+        mRoundedDrawable.setDrawable(mBitmapDrawable);
+        mRoundedDrawable.setClipEnabled(false);
+        mRoundedDrawable.setRadius(radius);
+        mRoundedDrawable.setBounds(bounds);
+        mRoundedDrawable.draw(mMockCanvas);
+        assertEquals(BITMAP_WIDTH - 10, mBitmapDrawable.getBounds().width());
+        assertEquals(BITMAP_HEIGHT - 10, mBitmapDrawable.getBounds().height());
+        assertEquals(bounds.centerX(), mBitmapDrawable.getBounds().centerX());
+        assertEquals(bounds.centerY(), mBitmapDrawable.getBounds().centerY());
+        // Background should also be drawn
+        verify(mMockCanvas)
+                .drawRoundRect(
+                        eq(new RectF(0, 0, bounds.width(), bounds.height())),
+                        eq((float) radius),
+                        eq((float) radius),
+                        any(Paint.class));
+    }
+}