[automerger] Media-compat-test: Change folder structure am: bad7671110

Change-Id: I3d11f7a447ce3a7966e33cc119a226d4ea42ead6
diff --git a/CleanSpec.mk b/CleanSpec.mk
index 7b2174e..dafdd94 100644
--- a/CleanSpec.mk
+++ b/CleanSpec.mk
@@ -46,6 +46,7 @@
 
 $(call add-clean-step, rm -rf $(OUT_DIR)/target/common/obj/JAVA_LIBRARIES/android-support-v*)
 $(call add-clean-step, rm -rf $(OUT_DIR)/target/common/obj/support.aidl)
+$(call add-clean-step, rm -rf $(OUT_DIR)/target/common/obj/JAVA_LIBRARIES/android-support-customtabs_intermediates/src/)
 
 # ************************************************
 # NEWER CLEAN STEPS MUST BE AT THE END OF THE LIST
diff --git a/buildSrc/init.gradle b/buildSrc/init.gradle
index ccbd35a..bfd4528 100644
--- a/buildSrc/init.gradle
+++ b/buildSrc/init.gradle
@@ -67,7 +67,7 @@
     ext.buildToolsVersion = '27.0.0'
     final String fullSdkPath = getFullSdkPath();
     if (file(fullSdkPath).exists()) {
-        gradle.ext.currentSdk = 26
+        gradle.ext.currentSdk = 28
         project.ext.androidJar =
                 files("${fullSdkPath}/platforms/android-${gradle.currentSdk}/android.jar")
         project.ext.androidSrcJar =
diff --git a/buildSrc/src/main/java/android/support/LibraryVersions.java b/buildSrc/src/main/java/android/support/LibraryVersions.java
index 27a52bd..efa0cba 100644
--- a/buildSrc/src/main/java/android/support/LibraryVersions.java
+++ b/buildSrc/src/main/java/android/support/LibraryVersions.java
@@ -23,7 +23,7 @@
     /**
      * Version code of the support library components.
      */
-    public static final Version SUPPORT_LIBRARY = new Version("27.1.0-SNAPSHOT");
+    public static final Version SUPPORT_LIBRARY = new Version("28.0.0-SNAPSHOT");
 
     /**
      * Version code for flatfoot 1.0 projects (room, lifecycles)
diff --git a/compat/src/main/java/android/support/v4/graphics/TypefaceCompat.java b/compat/src/main/java/android/support/v4/graphics/TypefaceCompat.java
index 3c55df6..734f183 100644
--- a/compat/src/main/java/android/support/v4/graphics/TypefaceCompat.java
+++ b/compat/src/main/java/android/support/v4/graphics/TypefaceCompat.java
@@ -35,7 +35,6 @@
 import android.support.v4.provider.FontsContractCompat;
 import android.support.v4.provider.FontsContractCompat.FontInfo;
 import android.support.v4.util.LruCache;
-
 /**
  * Helper for accessing features in {@link Typeface}.
  * @hide
@@ -46,7 +45,9 @@
 
     private static final TypefaceCompatImpl sTypefaceCompatImpl;
     static {
-        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
+        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) {
+            sTypefaceCompatImpl = new TypefaceCompatApi28Impl();
+        } else if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
             sTypefaceCompatImpl = new TypefaceCompatApi26Impl();
         } else if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N
                 && TypefaceCompatApi24Impl.isUsable()) {
diff --git a/compat/src/main/java/android/support/v4/graphics/TypefaceCompatApi26Impl.java b/compat/src/main/java/android/support/v4/graphics/TypefaceCompatApi26Impl.java
index 1b55a2e..00e31a1 100644
--- a/compat/src/main/java/android/support/v4/graphics/TypefaceCompatApi26Impl.java
+++ b/compat/src/main/java/android/support/v4/graphics/TypefaceCompatApi26Impl.java
@@ -60,76 +60,69 @@
             "createFromFamiliesWithDefault";
     private static final String FREEZE_METHOD = "freeze";
     private static final String ABORT_CREATION_METHOD = "abortCreation";
-    private static final Class sFontFamily;
-    private static final Constructor sFontFamilyCtor;
-    private static final Method sAddFontFromAssetManager;
-    private static final Method sAddFontFromBuffer;
-    private static final Method sFreeze;
-    private static final Method sAbortCreation;
-    private static final Method sCreateFromFamiliesWithDefault;
     private static final int RESOLVE_BY_FONT_TABLE = -1;
 
-    static {
-        Class fontFamilyClass;
+    protected final Class mFontFamily;
+    protected final Constructor mFontFamilyCtor;
+    protected final Method mAddFontFromAssetManager;
+    protected final Method mAddFontFromBuffer;
+    protected final Method mFreeze;
+    protected final Method mAbortCreation;
+    protected final Method mCreateFromFamiliesWithDefault;
+
+    public TypefaceCompatApi26Impl() {
+        Class fontFamily;
         Constructor fontFamilyCtor;
-        Method addFontMethod;
-        Method addFromBufferMethod;
-        Method freezeMethod;
-        Method abortCreationMethod;
-        Method createFromFamiliesWithDefaultMethod;
+        Method addFontFromAssetManager;
+        Method addFontFromBuffer;
+        Method freeze;
+        Method abortCreation;
+        Method createFromFamiliesWithDefault;
         try {
-            fontFamilyClass = Class.forName(FONT_FAMILY_CLASS);
-            fontFamilyCtor = fontFamilyClass.getConstructor();
-            addFontMethod = fontFamilyClass.getMethod(ADD_FONT_FROM_ASSET_MANAGER_METHOD,
-                    AssetManager.class, String.class, Integer.TYPE, Boolean.TYPE, Integer.TYPE,
-                    Integer.TYPE, Integer.TYPE, FontVariationAxis[].class);
-            addFromBufferMethod = fontFamilyClass.getMethod(ADD_FONT_FROM_BUFFER_METHOD,
-                    ByteBuffer.class, Integer.TYPE, FontVariationAxis[].class, Integer.TYPE,
-                    Integer.TYPE);
-            freezeMethod = fontFamilyClass.getMethod(FREEZE_METHOD);
-            abortCreationMethod = fontFamilyClass.getMethod(ABORT_CREATION_METHOD);
-            Object familyArray = Array.newInstance(fontFamilyClass, 1);
-            createFromFamiliesWithDefaultMethod =
-                    Typeface.class.getDeclaredMethod(CREATE_FROM_FAMILIES_WITH_DEFAULT_METHOD,
-                            familyArray.getClass(), Integer.TYPE, Integer.TYPE);
-            createFromFamiliesWithDefaultMethod.setAccessible(true);
+            fontFamily = obtainFontFamily();
+            fontFamilyCtor = obtainFontFamilyCtor(fontFamily);
+            addFontFromAssetManager = obtainAddFontFromAssetManagerMethod(fontFamily);
+            addFontFromBuffer = obtainAddFontFromBufferMethod(fontFamily);
+            freeze = obtainFreezeMethod(fontFamily);
+            abortCreation = obtainAbortCreationMethod(fontFamily);
+            createFromFamiliesWithDefault = obtainCreateFromFamiliesWithDefaultMethod(fontFamily);
         } catch (ClassNotFoundException | NoSuchMethodException e) {
             Log.e(TAG, "Unable to collect necessary methods for class " + e.getClass().getName(),
                     e);
-            fontFamilyClass = null;
+            fontFamily = null;
             fontFamilyCtor = null;
-            addFontMethod = null;
-            addFromBufferMethod = null;
-            freezeMethod = null;
-            abortCreationMethod = null;
-            createFromFamiliesWithDefaultMethod = null;
+            addFontFromAssetManager = null;
+            addFontFromBuffer = null;
+            freeze = null;
+            abortCreation = null;
+            createFromFamiliesWithDefault = null;
         }
-        sFontFamilyCtor = fontFamilyCtor;
-        sFontFamily = fontFamilyClass;
-        sAddFontFromAssetManager = addFontMethod;
-        sAddFontFromBuffer = addFromBufferMethod;
-        sFreeze = freezeMethod;
-        sAbortCreation = abortCreationMethod;
-        sCreateFromFamiliesWithDefault = createFromFamiliesWithDefaultMethod;
+        mFontFamily = fontFamily;
+        mFontFamilyCtor = fontFamilyCtor;
+        mAddFontFromAssetManager = addFontFromAssetManager;
+        mAddFontFromBuffer = addFontFromBuffer;
+        mFreeze = freeze;
+        mAbortCreation = abortCreation;
+        mCreateFromFamiliesWithDefault = createFromFamiliesWithDefault;
     }
 
     /**
-     * Returns true if API26 implementation is usable.
+     * Returns true if all the necessary methods were found.
      */
-    private static boolean isFontFamilyPrivateAPIAvailable() {
-        if (sAddFontFromAssetManager == null) {
+    private boolean isFontFamilyPrivateAPIAvailable() {
+        if (mAddFontFromAssetManager == null) {
             Log.w(TAG, "Unable to collect necessary private methods. "
                     + "Fallback to legacy implementation.");
         }
-        return sAddFontFromAssetManager != null;
+        return mAddFontFromAssetManager != null;
     }
 
     /**
      * Create a new FontFamily instance
      */
-    private static Object newFamily() {
+    private Object newFamily() {
         try {
-            return sFontFamilyCtor.newInstance();
+            return mFontFamilyCtor.newInstance();
         } catch (IllegalAccessException | InstantiationException | InvocationTargetException e) {
             throw new RuntimeException(e);
         }
@@ -139,10 +132,10 @@
      * Call FontFamily#addFontFromAssetManager(AssetManager mgr, String path, int cookie,
      *      boolean isAsset, int ttcIndex, int weight, int isItalic, FontVariationAxis[] axes)
      */
-    private static boolean addFontFromAssetManager(Context context, Object family, String fileName,
+    private boolean addFontFromAssetManager(Context context, Object family, String fileName,
             int ttcIndex, int weight, int style) {
         try {
-            final Boolean result = (Boolean) sAddFontFromAssetManager.invoke(family,
+            final Boolean result = (Boolean) mAddFontFromAssetManager.invoke(family,
                     context.getAssets(), fileName, 0 /* cookie */, false /* isAsset */, ttcIndex,
                     weight, style, null /* axes */);
             return result.booleanValue();
@@ -155,10 +148,10 @@
      * Call FontFamily#addFontFromBuffer(ByteBuffer font, int ttcIndex, FontVariationAxis[] axes,
      *      int weight, int italic)
      */
-    private static boolean addFontFromBuffer(Object family, ByteBuffer buffer,
+    private boolean addFontFromBuffer(Object family, ByteBuffer buffer,
             int ttcIndex, int weight, int style) {
         try {
-            final Boolean result = (Boolean) sAddFontFromBuffer.invoke(family,
+            final Boolean result = (Boolean) mAddFontFromBuffer.invoke(family,
                     buffer, ttcIndex, null /* axes */, weight, style);
             return result.booleanValue();
         } catch (IllegalAccessException | InvocationTargetException e) {
@@ -167,14 +160,14 @@
     }
 
     /**
-     * Call static method Typeface#createFromFamiliesWithDefault(
+     * Call method Typeface#createFromFamiliesWithDefault(
      *      FontFamily[] families, int weight, int italic)
      */
-    private static Typeface createFromFamiliesWithDefault(Object family) {
+    protected Typeface createFromFamiliesWithDefault(Object family) {
         try {
-            Object familyArray = Array.newInstance(sFontFamily, 1);
+            Object familyArray = Array.newInstance(mFontFamily, 1);
             Array.set(familyArray, 0, family);
-            return (Typeface) sCreateFromFamiliesWithDefault.invoke(null /* static method */,
+            return (Typeface) mCreateFromFamiliesWithDefault.invoke(null /* static method */,
                     familyArray, RESOLVE_BY_FONT_TABLE, RESOLVE_BY_FONT_TABLE);
         } catch (IllegalAccessException | InvocationTargetException e) {
             throw new RuntimeException(e);
@@ -184,9 +177,9 @@
     /**
      * Call FontFamily#freeze()
      */
-    private static boolean freeze(Object family) {
+    private boolean freeze(Object family) {
         try {
-            Boolean result = (Boolean) sFreeze.invoke(family);
+            Boolean result = (Boolean) mFreeze.invoke(family);
             return result.booleanValue();
         } catch (IllegalAccessException | InvocationTargetException e) {
             throw new RuntimeException(e);
@@ -196,9 +189,9 @@
     /**
      * Call FontFamily#abortCreation()
      */
-    private static boolean abortCreation(Object family) {
+    private boolean abortCreation(Object family) {
         try {
-            Boolean result = (Boolean) sAbortCreation.invoke(family);
+            Boolean result = (Boolean) mAbortCreation.invoke(family);
             return result.booleanValue();
         } catch (IllegalAccessException | InvocationTargetException e) {
             throw new RuntimeException(e);
@@ -299,4 +292,47 @@
         }
         return createFromFamiliesWithDefault(fontFamily);
     }
+
+    // The following getters retrieve by reflection the Typeface methods, belonging to the
+    // framework code, which will be invoked. Since the definitions of these methods can change
+    // across different API versions, inheriting classes should override these getters in order to
+    // reflect the method definitions in the API versions they represent.
+    //===========================================================================================
+    protected Class obtainFontFamily() throws ClassNotFoundException {
+        return Class.forName(FONT_FAMILY_CLASS);
+    }
+
+    protected Constructor obtainFontFamilyCtor(Class fontFamily) throws NoSuchMethodException {
+        return fontFamily.getConstructor();
+    }
+
+    protected Method obtainAddFontFromAssetManagerMethod(Class fontFamily)
+            throws NoSuchMethodException {
+        return fontFamily.getMethod(ADD_FONT_FROM_ASSET_MANAGER_METHOD,
+                AssetManager.class, String.class, Integer.TYPE, Boolean.TYPE, Integer.TYPE,
+                Integer.TYPE, Integer.TYPE, FontVariationAxis[].class);
+    }
+
+    protected Method obtainAddFontFromBufferMethod(Class fontFamily) throws NoSuchMethodException {
+        return fontFamily.getMethod(ADD_FONT_FROM_BUFFER_METHOD,
+                ByteBuffer.class, Integer.TYPE, FontVariationAxis[].class, Integer.TYPE,
+                Integer.TYPE);
+    }
+
+    protected Method obtainFreezeMethod(Class fontFamily) throws NoSuchMethodException {
+        return fontFamily.getMethod(FREEZE_METHOD);
+    }
+
+    protected Method obtainAbortCreationMethod(Class fontFamily) throws NoSuchMethodException {
+        return fontFamily.getMethod(ABORT_CREATION_METHOD);
+    }
+
+    protected Method obtainCreateFromFamiliesWithDefaultMethod(Class fontFamily)
+            throws NoSuchMethodException {
+        Object familyArray = Array.newInstance(fontFamily, 1);
+        Method m =  Typeface.class.getDeclaredMethod(CREATE_FROM_FAMILIES_WITH_DEFAULT_METHOD,
+                familyArray.getClass(), Integer.TYPE, Integer.TYPE);
+        m.setAccessible(true);
+        return m;
+    }
 }
diff --git a/compat/src/main/java/android/support/v4/graphics/TypefaceCompatApi28Impl.java b/compat/src/main/java/android/support/v4/graphics/TypefaceCompatApi28Impl.java
new file mode 100644
index 0000000..baa2ce6
--- /dev/null
+++ b/compat/src/main/java/android/support/v4/graphics/TypefaceCompatApi28Impl.java
@@ -0,0 +1,68 @@
+/*
+ * 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 android.support.v4.graphics;
+
+import static android.support.annotation.RestrictTo.Scope.LIBRARY_GROUP;
+
+import android.graphics.Typeface;
+import android.support.annotation.RequiresApi;
+import android.support.annotation.RestrictTo;
+
+import java.lang.reflect.Array;
+import java.lang.reflect.InvocationTargetException;
+import java.lang.reflect.Method;
+
+/**
+ * Implementation of the Typeface compat methods for API 28 and above.
+ * @hide
+ */
+@RestrictTo(LIBRARY_GROUP)
+@RequiresApi(28)
+public class TypefaceCompatApi28Impl extends TypefaceCompatApi26Impl {
+    private static final String TAG = "TypefaceCompatApi28Impl";
+
+    private static final String CREATE_FROM_FAMILIES_WITH_DEFAULT_METHOD =
+            "createFromFamiliesWithDefault";
+    private static final int RESOLVE_BY_FONT_TABLE = -1;
+    private static final String DEFAULT_FAMILY = "sans-serif";
+
+    /**
+     * Call method Typeface#createFromFamiliesWithDefault(
+     *      FontFamily[] families, String fallbackName, int weight, int italic)
+     */
+    @Override
+    protected Typeface createFromFamiliesWithDefault(Object family) {
+        try {
+            Object familyArray = Array.newInstance(mFontFamily, 1);
+            Array.set(familyArray, 0, family);
+            return (Typeface) mCreateFromFamiliesWithDefault.invoke(null /* static method */,
+                    familyArray, DEFAULT_FAMILY, RESOLVE_BY_FONT_TABLE, RESOLVE_BY_FONT_TABLE);
+        } catch (IllegalAccessException | InvocationTargetException e) {
+            throw new RuntimeException(e);
+        }
+    }
+
+    @Override
+    protected Method obtainCreateFromFamiliesWithDefaultMethod(Class fontFamily)
+            throws NoSuchMethodException {
+        Object familyArray = Array.newInstance(fontFamily, 1);
+        Method m =  Typeface.class.getDeclaredMethod(CREATE_FROM_FAMILIES_WITH_DEFAULT_METHOD,
+                familyArray.getClass(), String.class, Integer.TYPE, Integer.TYPE);
+        m.setAccessible(true);
+        return m;
+    }
+}
diff --git a/design/api/27.0.0-SNAPSHOT.ignore b/design/api/27.0.0-SNAPSHOT.ignore
new file mode 100644
index 0000000..533cc49
--- /dev/null
+++ b/design/api/27.0.0-SNAPSHOT.ignore
@@ -0,0 +1,5 @@
+197ce1d
+88bc57e
+9761c3e
+86b38bf
+c6abd5e
diff --git a/jetifier/.gitignore b/jetifier/.gitignore
new file mode 100644
index 0000000..4469528
--- /dev/null
+++ b/jetifier/.gitignore
@@ -0,0 +1 @@
+**/build
diff --git a/jetifier/jetifier/build.gradle b/jetifier/jetifier/build.gradle
new file mode 100644
index 0000000..752e3aa
--- /dev/null
+++ b/jetifier/jetifier/build.gradle
@@ -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
+ */
+
+group 'android.support.tools.jetifier'
+version '1.0'
+
+buildscript {
+    ext.supportRootFolder = "${project.projectDir}/../../"
+    apply from: "$supportRootFolder/buildSrc/repos.gradle"
+
+    ext.kotlin_version = '1.1.3'
+
+    repos.addMavenRepositories(repositories)
+
+    dependencies {
+        classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
+    }
+}
+
+subprojects {
+    group 'android.support.tools.jetifier'
+    version '1.0'
+
+    ext.supportRootFolder = "${project.projectDir}/../../.."
+
+    apply plugin: 'kotlin'
+    apply plugin: 'java'
+    apply from: "$supportRootFolder/buildSrc/repos.gradle"
+
+    compileKotlin {
+        kotlinOptions.jvmTarget = "1.8"
+    }
+    compileTestKotlin {
+        kotlinOptions.jvmTarget = "1.8"
+    }
+
+    repos.addMavenRepositories(repositories)
+
+    dependencies {
+        compile "org.jetbrains.kotlin:kotlin-stdlib:$kotlin_version"
+    }
+}
diff --git a/jetifier/jetifier/core/build.gradle b/jetifier/jetifier/core/build.gradle
new file mode 100644
index 0000000..1ac3f36
--- /dev/null
+++ b/jetifier/jetifier/core/build.gradle
@@ -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
+ */
+
+version '1.0'
+
+dependencies {
+    compile group: 'org.ow2.asm', name: 'asm', version: '5.2'
+    compile group: 'org.ow2.asm', name: 'asm-commons', version: '5.2'
+    compile group: 'com.google.code.gson', name: 'gson', version: '2.8.0'
+    compile group: 'org.jdom', name: 'jdom2', version: '2.0.6'
+    testCompile group: 'junit', name: 'junit', version: '4.12'
+    testCompile group: 'com.google.truth', name: 'truth', version: '0.31'
+}
diff --git a/jetifier/jetifier/core/src/main/kotlin/android/support/tools/jetifier/core/Processor.kt b/jetifier/jetifier/core/src/main/kotlin/android/support/tools/jetifier/core/Processor.kt
new file mode 100644
index 0000000..9d74267
--- /dev/null
+++ b/jetifier/jetifier/core/src/main/kotlin/android/support/tools/jetifier/core/Processor.kt
@@ -0,0 +1,142 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *       http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR 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.tools.jetifier.core
+
+import android.support.tools.jetifier.core.archive.Archive
+import android.support.tools.jetifier.core.archive.ArchiveFile
+import android.support.tools.jetifier.core.archive.ArchiveItemVisitor
+import android.support.tools.jetifier.core.config.Config
+import android.support.tools.jetifier.core.transform.TransformationContext
+import android.support.tools.jetifier.core.transform.Transformer
+import android.support.tools.jetifier.core.transform.bytecode.ByteCodeTransformer
+import android.support.tools.jetifier.core.transform.pom.PomDocument
+import android.support.tools.jetifier.core.transform.pom.PomScanner
+import android.support.tools.jetifier.core.transform.resource.XmlResourcesTransformer
+import android.support.tools.jetifier.core.utils.Log
+import java.nio.file.Files
+import java.nio.file.Path
+
+/**
+ * The main entry point to the library. Extracts any given archive recursively and runs all
+ * the registered [Transformer]s over the set and creates new archives that will contain the
+ * transformed files.
+ */
+class Processor(private val config : Config) : ArchiveItemVisitor {
+
+    companion object {
+        private const val TAG = "Processor"
+    }
+
+    private val context = TransformationContext(config)
+
+    private val transformers = listOf(
+            // Register your transformers here
+            ByteCodeTransformer(context),
+            XmlResourcesTransformer(context)
+    )
+
+    /**
+     * Transforms the input libraries given in [inputLibraries] using all the registered
+     * [Transformer]s and returns new libraries stored in [outputPath].
+     *
+     * Currently we have the following transformers:
+     * - [ByteCodeTransformer] for java native code
+     * - [XmlResourcesTransformer] for java native code
+     */
+    fun transform(inputLibraries: List<Path>, outputPath: Path) {
+        // 1) Extract and load all libraries
+        val libraries = loadLibraries(inputLibraries)
+
+        // 2) Search for POM files
+        val pomFiles = scanPomFiles(libraries)
+
+        // 3) Transform all the libraries
+        libraries.forEach{ transformLibrary(it) }
+
+        if (context.wasErrorFound()) {
+            throw IllegalArgumentException("There were ${context.mappingNotFoundFailuresCount}" +
+                " errors found during the remapping. Check the logs for more details.")
+        }
+
+        // TODO: Here we might need to modify the POM files if they point at a library that we have
+        // just refactored.
+
+        // 4) Transform the previously discovered POM files
+        transformPomFiles(pomFiles)
+
+        // 5) Repackage the libraries back to archives
+        libraries.forEach{ it.writeSelfToDir(outputPath) }
+
+        return
+    }
+
+    private fun loadLibraries(inputLibraries : List<Path>) : List<Archive> {
+        val libraries = mutableListOf<Archive>()
+        for (libraryPath in inputLibraries) {
+            if (!Files.isReadable(libraryPath)) {
+                Log.e(TAG, "Cannot access the input file: '%s'", libraryPath)
+                continue
+            }
+
+            libraries.add(Archive.Builder.extract(libraryPath))
+        }
+        return libraries.toList()
+    }
+
+    private fun scanPomFiles(libraries: List<Archive>) : List<PomDocument> {
+        val scanner = PomScanner(config)
+
+        libraries.forEach { scanner.scanArchiveForPomFile(it) }
+        if (scanner.wasErrorFound()) {
+            throw IllegalArgumentException("At least one of the libraries depends on an older" +
+                " version of support library. Check the logs for more details.")
+        }
+
+        return scanner.pomFiles
+    }
+
+    private fun transformPomFiles(files: List<PomDocument>) {
+        files.forEach {
+            it.applyRules(config.pomRewriteRules)
+            it.saveBackToFileIfNeeded()
+        }
+    }
+
+    private fun transformLibrary(archive: Archive) {
+        Log.i(TAG, "Started new transformation")
+        Log.i(TAG, "- Input file: %s", archive.relativePath)
+
+        archive.accept(this)
+    }
+
+    override fun visit(archive: Archive) {
+        archive.files.forEach{ it.accept(this) }
+    }
+
+    override fun visit(archiveFile: ArchiveFile) {
+        val transformer = transformers.firstOrNull { it.canTransform(archiveFile) }
+
+        if (transformer == null) {
+            Log.i(TAG, "[Skipped] %s", archiveFile.relativePath)
+            return
+        }
+
+        Log.i(TAG, "[Applied: %s] %s", transformer.javaClass.simpleName, archiveFile.relativePath)
+        transformer.runTransform(archiveFile)
+    }
+
+}
\ No newline at end of file
diff --git a/jetifier/jetifier/core/src/main/kotlin/android/support/tools/jetifier/core/archive/Archive.kt b/jetifier/jetifier/core/src/main/kotlin/android/support/tools/jetifier/core/archive/Archive.kt
new file mode 100644
index 0000000..19ebe62
--- /dev/null
+++ b/jetifier/jetifier/core/src/main/kotlin/android/support/tools/jetifier/core/archive/Archive.kt
@@ -0,0 +1,142 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *       http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR 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.tools.jetifier.core.archive
+
+import android.support.tools.jetifier.core.utils.Log
+import java.io.BufferedOutputStream
+import java.io.FileInputStream
+import java.io.FileOutputStream
+import java.io.IOException
+import java.io.InputStream
+import java.io.OutputStream
+import java.nio.file.Files
+import java.nio.file.Path
+import java.nio.file.Paths
+import java.util.zip.ZipEntry
+import java.util.zip.ZipInputStream
+import java.util.zip.ZipOutputStream
+
+/**
+ * Represents an archive (zip, jar, aar ...)
+ */
+class Archive(
+        override val relativePath: Path,
+        val files: List<ArchiveItem>)
+    : ArchiveItem {
+
+    companion object {
+        /** Defines file extensions that are recognized as archives */
+        val ARCHIVE_EXTENSIONS = listOf(".jar", ".zip", ".aar")
+
+        const val TAG = "Archive"
+    }
+
+    override val fileName: String = relativePath.fileName.toString()
+
+    override fun accept(visitor: ArchiveItemVisitor) {
+        visitor.visit(this)
+    }
+
+    @Throws(IOException::class)
+    fun writeSelfToDir(outputDirPath: Path) {
+        val outputPath = Paths.get(outputDirPath.toString(), fileName)
+
+        if (Files.exists(outputPath)) {
+            Log.i(TAG, "Deleting old output file")
+            Files.delete(outputPath)
+        }
+
+        // Create directories if they don't exist yet
+        Files.createDirectories(outputDirPath)
+
+        Log.i(TAG, "Writing archive: %s", outputPath.toUri())
+        Files.createFile(outputPath)
+        val stream = BufferedOutputStream(FileOutputStream(outputPath.toFile()))
+        writeSelfTo(stream)
+        stream.close()
+    }
+
+    @Throws(IOException::class)
+    override fun writeSelfTo(outputStream: OutputStream) {
+        val out = ZipOutputStream(outputStream)
+
+        for (file in files) {
+            Log.d(TAG, "Writing file: %s", file.relativePath)
+
+            val entry = ZipEntry(file.relativePath.toString())
+            out.putNextEntry(entry)
+            file.writeSelfTo(out)
+            out.closeEntry()
+        }
+        out.finish()
+    }
+
+
+    object Builder {
+
+        @Throws(IOException::class)
+        fun extract(archivePath: Path): Archive {
+            Log.i(TAG, "Extracting: %s", archivePath.toUri())
+
+            val inputStream = FileInputStream(archivePath.toFile())
+            inputStream.use {
+                val archive = extractArchive(it, archivePath)
+                return archive
+            }
+        }
+
+        @Throws(IOException::class)
+        private fun extractArchive(inputStream: InputStream, relativePath: Path)
+                : Archive {
+            val zipIn = ZipInputStream(inputStream)
+            val files = mutableListOf<ArchiveItem>()
+
+            var entry: ZipEntry? = zipIn.nextEntry
+            // iterates over entries in the zip file
+            while (entry != null) {
+                if (!entry.isDirectory) {
+                    val entryPath = Paths.get(entry.name)
+                    if (isArchive(entry)) {
+                        Log.i(TAG, "Extracting nested: %s", entryPath)
+                        files.add(extractArchive(zipIn, entryPath))
+                    } else {
+                        files.add(extractFile(zipIn, entryPath))
+                    }
+                }
+                zipIn.closeEntry()
+                entry = zipIn.nextEntry
+            }
+            // Cannot close the zip stream at this moment as that would close also any parent zip
+            // streams in case we are processing a nested archive.
+
+            return Archive(relativePath, files.toList())
+        }
+
+        @Throws(IOException::class)
+        private fun extractFile(zipIn: ZipInputStream, relativePath: Path): ArchiveFile {
+            Log.d(TAG, "Extracting archive: %s", relativePath)
+
+            val data = zipIn.readBytes()
+            return ArchiveFile(relativePath, data)
+        }
+
+        private fun isArchive(zipEntry: ZipEntry) : Boolean {
+            return ARCHIVE_EXTENSIONS.any { zipEntry.name.endsWith(it, ignoreCase = true) }
+        }
+
+    }
+}
\ No newline at end of file
diff --git a/jetifier/jetifier/core/src/main/kotlin/android/support/tools/jetifier/core/archive/ArchiveFile.kt b/jetifier/jetifier/core/src/main/kotlin/android/support/tools/jetifier/core/archive/ArchiveFile.kt
new file mode 100644
index 0000000..3054b71
--- /dev/null
+++ b/jetifier/jetifier/core/src/main/kotlin/android/support/tools/jetifier/core/archive/ArchiveFile.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.support.tools.jetifier.core.archive
+
+import java.io.IOException
+import java.io.OutputStream
+import java.nio.file.Path
+
+/**
+ * Represents a file in the archive that is not an archive.
+ */
+class ArchiveFile(override val relativePath: Path, var data: ByteArray) : ArchiveItem {
+
+    override val fileName: String = relativePath.fileName.toString()
+
+    override fun accept(visitor: ArchiveItemVisitor) {
+        visitor.visit(this)
+    }
+
+    @Throws(IOException::class)
+    override fun writeSelfTo(outputStream: OutputStream) {
+        outputStream.write(data)
+    }
+
+}
\ No newline at end of file
diff --git a/jetifier/jetifier/core/src/main/kotlin/android/support/tools/jetifier/core/archive/ArchiveItem.kt b/jetifier/jetifier/core/src/main/kotlin/android/support/tools/jetifier/core/archive/ArchiveItem.kt
new file mode 100644
index 0000000..2d35e13
--- /dev/null
+++ b/jetifier/jetifier/core/src/main/kotlin/android/support/tools/jetifier/core/archive/ArchiveItem.kt
@@ -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.support.tools.jetifier.core.archive
+
+import java.io.OutputStream
+import java.nio.file.Path
+
+/**
+ * Abstraction to represent archive and its files as a one thing.
+ */
+interface ArchiveItem {
+
+    /**
+     * Relative path of the item according to its location in the archive.
+     *
+     * Files in a nested archive have a path relative to that archive not to the parent of
+     * the archive. The root archive has the file system path set as its relative path.
+     */
+    val relativePath : Path
+
+    /**
+     * Name of the file.
+     */
+    val fileName : String
+
+    /**
+     * Accepts visitor.
+     */
+    fun accept(visitor: ArchiveItemVisitor)
+
+    /**
+     * Writes its internal data (or other nested files) into the given output stream.
+     */
+    fun writeSelfTo(outputStream: OutputStream)
+
+
+    fun isPomFile() = fileName.equals("pom.xml", ignoreCase = true)
+
+    fun isClassFile() = fileName.endsWith(".class", ignoreCase = true)
+
+    fun isXmlFile() = fileName.endsWith(".xml", ignoreCase = true)
+
+    fun isProGuardFile () = fileName.equals("proguard.txt", ignoreCase = true)
+
+}
\ No newline at end of file
diff --git a/jetifier/jetifier/core/src/main/kotlin/android/support/tools/jetifier/core/archive/ArchiveItemVisitor.kt b/jetifier/jetifier/core/src/main/kotlin/android/support/tools/jetifier/core/archive/ArchiveItemVisitor.kt
new file mode 100644
index 0000000..7c99fd9
--- /dev/null
+++ b/jetifier/jetifier/core/src/main/kotlin/android/support/tools/jetifier/core/archive/ArchiveItemVisitor.kt
@@ -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.support.tools.jetifier.core.archive
+
+/**
+ * Visitor for [ArchiveItem]
+ */
+interface ArchiveItemVisitor {
+
+    fun visit(archive: Archive)
+
+    fun visit(archiveFile: ArchiveFile)
+
+}
diff --git a/jetifier/jetifier/core/src/main/kotlin/android/support/tools/jetifier/core/config/Config.kt b/jetifier/jetifier/core/src/main/kotlin/android/support/tools/jetifier/core/config/Config.kt
new file mode 100644
index 0000000..886a202
--- /dev/null
+++ b/jetifier/jetifier/core/src/main/kotlin/android/support/tools/jetifier/core/config/Config.kt
@@ -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 android.support.tools.jetifier.core.config
+
+import android.support.tools.jetifier.core.rules.RewriteRule
+import android.support.tools.jetifier.core.transform.pom.PomRewriteRule
+import android.support.tools.jetifier.core.map.TypesMap
+import android.support.tools.jetifier.core.transform.proguard.ProGuardTypesMap
+import com.google.gson.annotations.SerializedName
+
+/**
+ * The main and only one configuration that is used by the tool and all its transformers.
+ *
+ * [restrictToPackagePrefixes] Package prefixes that limit the scope of the rewriting
+ * [rewriteRules] Rules to scan support libraries to generate [TypesMap]
+ * [pomRewriteRules] Rules to rewrite POM files
+ * [typesMap] Map of all java types and fields to be used to rewrite libraries.
+ */
+data class Config(
+        val restrictToPackagePrefixes: List<String>,
+        val rewriteRules: List<RewriteRule>,
+        val pomRewriteRules: List<PomRewriteRule>,
+        val typesMap: TypesMap,
+        val proGuardMap: ProGuardTypesMap) {
+
+    companion object {
+        /** Path to the default config file located within the jar file. */
+        const val DEFAULT_CONFIG_RES_PATH = "/default.config"
+    }
+
+    fun setNewMap(mappings: TypesMap) : Config {
+        return Config(
+            restrictToPackagePrefixes, rewriteRules, pomRewriteRules, mappings, proGuardMap)
+    }
+
+    /** Returns JSON data model of this class */
+    fun toJson() : JsonData {
+        return JsonData(
+            restrictToPackagePrefixes,
+            rewriteRules.map { it.toJson() }.toList(),
+            pomRewriteRules.map { it.toJson() }.toList(),
+            typesMap.toJson(),
+            proGuardMap.toJson()
+        )
+    }
+
+
+    /**
+     * JSON data model for [Config].
+     */
+    data class JsonData(
+            @SerializedName("restrictToPackagePrefixes")
+            val restrictToPackages: List<String?>,
+
+            @SerializedName("rules")
+            val rules: List<RewriteRule.JsonData?>,
+
+            @SerializedName("pomRules")
+            val pomRules: List<PomRewriteRule.JsonData?>,
+
+            @SerializedName("map")
+            val mappings: TypesMap.JsonData? = null,
+
+            @SerializedName("proGuardMap")
+            val proGuardMap: ProGuardTypesMap.JsonData? = null) {
+
+        /** Creates instance of [Config] */
+        fun toConfig() : Config {
+            return Config(
+                restrictToPackages.filterNotNull(),
+                rules.filterNotNull().map { it.toRule() },
+                pomRules.filterNotNull().map { it.toRule() },
+                mappings?.toMappings() ?: TypesMap.EMPTY,
+                proGuardMap?.toMappings() ?: ProGuardTypesMap.EMPTY
+            )
+        }
+    }
+
+}
diff --git a/jetifier/jetifier/core/src/main/kotlin/android/support/tools/jetifier/core/config/ConfigParser.kt b/jetifier/jetifier/core/src/main/kotlin/android/support/tools/jetifier/core/config/ConfigParser.kt
new file mode 100644
index 0000000..ee6e6c3
--- /dev/null
+++ b/jetifier/jetifier/core/src/main/kotlin/android/support/tools/jetifier/core/config/ConfigParser.kt
@@ -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.support.tools.jetifier.core.config
+
+import android.support.tools.jetifier.core.utils.Log
+import com.google.gson.GsonBuilder
+import java.io.FileWriter
+import java.nio.file.Files
+import java.nio.file.Path
+
+object ConfigParser {
+
+    private const val TAG : String = "Config"
+
+    private val gson = GsonBuilder().setPrettyPrinting().create()
+
+    fun writeToString(config: Config) : String {
+        return gson.toJson(config.toJson())
+    }
+
+    fun writeToFile(config: Config, outputPath: Path) {
+        FileWriter(outputPath.toFile()).use {
+            gson.toJson(config.toJson(), it)
+        }
+    }
+
+    fun parseFromString(inputText: String) : Config? {
+        return gson.fromJson(inputText, Config.JsonData::class.java).toConfig()
+    }
+
+    fun loadFromFile(configPath: Path) : Config? {
+        return loadConfigFileInternal(configPath)
+    }
+
+    fun loadDefaultConfig() : Config? {
+        Log.v(TAG, "Using the default config '%s'", Config.DEFAULT_CONFIG_RES_PATH)
+
+        val inputStream = javaClass.getResourceAsStream(Config.DEFAULT_CONFIG_RES_PATH)
+        return parseFromString(inputStream.reader().readText())
+    }
+
+    private fun loadConfigFileInternal(configPath: Path) : Config? {
+        if (!Files.isReadable(configPath)) {
+            Log.e(TAG, "Cannot access the config file: '%s'", configPath)
+            return null
+        }
+
+        Log.i(TAG, "Parsing config file: '%s'", configPath.toUri())
+        val config = parseFromString(configPath.toFile().readText())
+
+        if (config == null) {
+            Log.e(TAG, "Failed to parseFromString the config file")
+            return null
+        }
+
+        return config
+    }
+}
diff --git a/jetifier/jetifier/core/src/main/kotlin/android/support/tools/jetifier/core/map/LibraryMapGenerator.kt b/jetifier/jetifier/core/src/main/kotlin/android/support/tools/jetifier/core/map/LibraryMapGenerator.kt
new file mode 100644
index 0000000..de5a17f
--- /dev/null
+++ b/jetifier/jetifier/core/src/main/kotlin/android/support/tools/jetifier/core/map/LibraryMapGenerator.kt
@@ -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.support.tools.jetifier.core.map
+
+import android.support.tools.jetifier.core.archive.Archive
+import android.support.tools.jetifier.core.archive.ArchiveFile
+import android.support.tools.jetifier.core.archive.ArchiveItemVisitor
+import android.support.tools.jetifier.core.config.Config
+import android.support.tools.jetifier.core.transform.Transformer
+import org.objectweb.asm.ClassReader
+import org.objectweb.asm.ClassWriter
+
+/**
+ * Scans a library java files using [MapGeneratorRemapper] to create [TypesMap].
+ */
+class LibraryMapGenerator constructor(config: Config) : ArchiveItemVisitor {
+
+    val remapper = MapGeneratorRemapper(config)
+
+    /**
+     * Scans the given [library] to extend the types map meta-data. The final map can be retrieved
+     * using [generateMap].
+     */
+    fun scanLibrary(library: Archive) {
+        library.accept(this)
+    }
+
+    /**
+     * Creates the [TypesMap] based on the meta-data aggregated via previous [scanFile] calls
+     */
+    fun generateMap() : TypesMap {
+        return remapper.createTypesMap()
+    }
+
+    override fun visit(archive: Archive) {
+        archive.files.forEach{ it.accept(this) }
+    }
+
+    override fun visit(archiveFile: ArchiveFile) {
+        if (archiveFile.isClassFile()) {
+            scanFile(archiveFile)
+        }
+    }
+
+    private fun scanFile(file: ArchiveFile) {
+        val reader = ClassReader(file.data)
+        val writer = ClassWriter(0 /* flags */)
+
+        val visitor = remapper.createClassRemapper(writer)
+
+        reader.accept(visitor, 0 /* flags */)
+    }
+
+}
\ No newline at end of file
diff --git a/jetifier/jetifier/core/src/main/kotlin/android/support/tools/jetifier/core/map/MapGeneratorRemapper.kt b/jetifier/jetifier/core/src/main/kotlin/android/support/tools/jetifier/core/map/MapGeneratorRemapper.kt
new file mode 100644
index 0000000..43436f6
--- /dev/null
+++ b/jetifier/jetifier/core/src/main/kotlin/android/support/tools/jetifier/core/map/MapGeneratorRemapper.kt
@@ -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.support.tools.jetifier.core.map
+
+import android.support.tools.jetifier.core.config.Config
+import android.support.tools.jetifier.core.rules.JavaField
+import android.support.tools.jetifier.core.rules.JavaType
+import android.support.tools.jetifier.core.transform.bytecode.CoreRemapper
+import android.support.tools.jetifier.core.transform.bytecode.asm.CustomClassRemapper
+import android.support.tools.jetifier.core.transform.bytecode.asm.CustomRemapper
+import android.support.tools.jetifier.core.utils.Log
+import org.objectweb.asm.ClassVisitor
+import java.util.regex.Pattern
+
+/**
+ * Hooks to asm remapping to collect data for [TypesMap] by applying all the [RewriteRule]s from the
+ * given [config] on all discovered and eligible types and fields.
+ */
+class MapGeneratorRemapper(private val config: Config) : CoreRemapper {
+
+    companion object {
+        private const val TAG : String = "MapGeneratorRemapper"
+    }
+
+    private val typesRewritesMap = hashMapOf<JavaType, JavaType>()
+    private val fieldsRewritesMap = hashMapOf<JavaField, JavaField>()
+
+    var isMapNotComplete = false
+        private set
+
+    /**
+     * Ignore mPrefix types and anything that contains $ as these are internal fields that won't be
+     * ever referenced.
+     */
+    private val ignoredFields = Pattern.compile("(^m[A-Z]+.*$)|(.*\\$.*)")
+
+    fun createClassRemapper(visitor: ClassVisitor): CustomClassRemapper {
+        return CustomClassRemapper(visitor, CustomRemapper(this))
+    }
+
+    override fun rewriteType(type: JavaType): JavaType {
+        if (!isTypeSupported(type)) {
+            return type
+        }
+
+        if (typesRewritesMap.contains(type)) {
+            return type
+        }
+
+        // Try to find a rule
+        for (rule in config.rewriteRules) {
+            val mappedTypeName = rule.apply(type) ?: continue
+            typesRewritesMap.put(type, mappedTypeName)
+
+            Log.i(TAG, "  map: %s -> %s", type, mappedTypeName)
+            return mappedTypeName
+        }
+
+        isMapNotComplete = true
+        Log.e(TAG, "No rule for: " + type)
+        typesRewritesMap.put(type, type) // Identity
+        return type
+    }
+
+    override fun rewriteField(field : JavaField): JavaField {
+        if (!isTypeSupported(field.owner)) {
+            return field
+        }
+
+        if (ignoredFields.matcher(field.name).matches()) {
+            return field
+        }
+
+        if (fieldsRewritesMap.contains(field)) {
+            return field
+        }
+
+        // Try to find a rule
+        for (rule in config.rewriteRules) {
+            val mappedFieldName = rule.apply(field) ?: continue
+            fieldsRewritesMap.put(field, mappedFieldName)
+
+            Log.i(TAG, "  map: %s -> %s", field, mappedFieldName)
+            return mappedFieldName
+        }
+
+        isMapNotComplete = true
+        Log.e(TAG, "No rule for: " + field)
+        fieldsRewritesMap.put(field, field) // Identity
+        return field
+    }
+
+    fun createTypesMap() : TypesMap {
+        return TypesMap(typesRewritesMap, fieldsRewritesMap)
+    }
+
+    private fun isTypeSupported(type: JavaType) : Boolean {
+        return config.restrictToPackagePrefixes.any{ type.fullName.startsWith(it) }
+    }
+}
\ No newline at end of file
diff --git a/jetifier/jetifier/core/src/main/kotlin/android/support/tools/jetifier/core/map/TypesMap.kt b/jetifier/jetifier/core/src/main/kotlin/android/support/tools/jetifier/core/map/TypesMap.kt
new file mode 100644
index 0000000..ce02026
--- /dev/null
+++ b/jetifier/jetifier/core/src/main/kotlin/android/support/tools/jetifier/core/map/TypesMap.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.support.tools.jetifier.core.map
+
+import android.support.tools.jetifier.core.rules.JavaField
+import android.support.tools.jetifier.core.rules.JavaType
+import android.support.tools.jetifier.core.rules.RewriteRule
+
+/**
+ * Contains all the mappings needed to rewrite java types and fields.
+ *
+ * These mappings are generated by the preprocessor from existing support libraries and by applying
+ * the given [RewriteRule]s.
+ */
+data class TypesMap(
+        val types: Map<JavaType, JavaType>,
+        val fields: Map<JavaField, JavaField>) {
+
+    companion object {
+        val EMPTY = TypesMap(emptyMap(), emptyMap())
+    }
+
+    /** Returns JSON data model of this class */
+    fun toJson() : JsonData {
+        return JsonData(
+                types = types.map { it.key.fullName to it.value.fullName }
+                        .toMap(),
+                fields = mapFields())
+    }
+
+    private fun mapFields() : Map<String, Map<String, List<String>>> {
+        val rawMap = mutableMapOf<String, MutableMap<String, MutableList<String>>>()
+
+        fields.forEach{
+            rawMap
+                .getOrPut(it.key.owner.fullName, { mutableMapOf<String, MutableList<String>>()} )
+                .getOrPut(it.value.owner.fullName, { mutableListOf() })
+                .add(it.key.name)
+        }
+        return rawMap
+    }
+
+    /**
+     * JSON data model for [TypesMap].
+     */
+    data class JsonData(
+            val types: Map<String, String>,
+            val fields: Map<String, Map<String, List<String>>>)  {
+
+        /** Creates instance of [TypesMap] */
+        fun toMappings() : TypesMap {
+            return TypesMap(
+                types = types
+                    .orEmpty()
+                    .map { JavaType(it.key) to JavaType(it.value) }
+                    .toMap(),
+                fields = fields
+                    .orEmpty().entries
+                    .flatMap {
+                        top ->
+                        top.value.flatMap {
+                            mid ->
+                            mid.value.map {
+                                JavaField(top.key, it) to JavaField(mid.key, it)
+                            }
+                        }
+                    }
+                    .toMap())
+        }
+    }
+}
+
diff --git a/jetifier/jetifier/core/src/main/kotlin/android/support/tools/jetifier/core/rules/JavaField.kt b/jetifier/jetifier/core/src/main/kotlin/android/support/tools/jetifier/core/rules/JavaField.kt
new file mode 100644
index 0000000..c423f0a
--- /dev/null
+++ b/jetifier/jetifier/core/src/main/kotlin/android/support/tools/jetifier/core/rules/JavaField.kt
@@ -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.tools.jetifier.core.rules
+
+/**
+ * Wrapper for Java field declaration.
+ */
+data class JavaField(val owner : JavaType, val name : String) {
+
+    constructor(owner : String, name : String) : this(JavaType(owner), name)
+
+
+    fun renameOwner(newOwner: JavaType) = JavaField(newOwner, name)
+
+    override fun toString() = owner.toString() + "." + name
+
+}
\ No newline at end of file
diff --git a/jetifier/jetifier/core/src/main/kotlin/android/support/tools/jetifier/core/rules/JavaType.kt b/jetifier/jetifier/core/src/main/kotlin/android/support/tools/jetifier/core/rules/JavaType.kt
new file mode 100644
index 0000000..d7a077b
--- /dev/null
+++ b/jetifier/jetifier/core/src/main/kotlin/android/support/tools/jetifier/core/rules/JavaType.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.support.tools.jetifier.core.rules
+
+/**
+ * Wrapper for Java type declaration.
+ */
+data class JavaType(val fullName: String) {
+
+    init {
+        if (fullName.contains('.')) {
+            throw IllegalArgumentException("The type does not support '.' as package separator!")
+        }
+    }
+
+    companion object {
+        /** Creates the type from notation where packages are separated using '.' */
+        fun fromDotVersion(fullName: String) : JavaType {
+            return JavaType(fullName.replace('.', '/'))
+        }
+    }
+
+    /** Returns the type as a string where packages are separated using '.' */
+    fun toDotNotation() : String {
+        return fullName.replace('/', '.')
+    }
+
+
+    override fun toString() = fullName
+
+}
\ No newline at end of file
diff --git a/jetifier/jetifier/core/src/main/kotlin/android/support/tools/jetifier/core/rules/JavaTypeXmlRef.kt b/jetifier/jetifier/core/src/main/kotlin/android/support/tools/jetifier/core/rules/JavaTypeXmlRef.kt
new file mode 100644
index 0000000..9d58046
--- /dev/null
+++ b/jetifier/jetifier/core/src/main/kotlin/android/support/tools/jetifier/core/rules/JavaTypeXmlRef.kt
@@ -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.support.tools.jetifier.core.rules
+
+/**
+ * Wrapper for Java type reference used in XML.
+ *
+ * In XML we use '.' as a package separator.
+ */
+data class JavaTypeXmlRef(val fullName : String) {
+
+    constructor(type: JavaType)
+        : this(type.fullName.replace('/', '.'))
+
+    fun toJavaType() : JavaType {
+        return JavaType(fullName.replace('.', '/'))
+    }
+
+    override fun toString() = fullName
+}
\ No newline at end of file
diff --git a/jetifier/jetifier/core/src/main/kotlin/android/support/tools/jetifier/core/rules/RewriteRule.kt b/jetifier/jetifier/core/src/main/kotlin/android/support/tools/jetifier/core/rules/RewriteRule.kt
new file mode 100644
index 0000000..700e757
--- /dev/null
+++ b/jetifier/jetifier/core/src/main/kotlin/android/support/tools/jetifier/core/rules/RewriteRule.kt
@@ -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.support.tools.jetifier.core.rules
+
+import com.google.gson.annotations.SerializedName
+import java.util.regex.Pattern
+
+/**
+ * Rule that rewrites a Java type or field based on the given arguments.
+ *
+ * Used in the preprocessor when generating [TypesMap].
+ *
+ * @param from Regular expression where packages are separated via '/' and inner class separator
+ * is "$". Used to match the input type.
+ * @param to A string to be used as a replacement if the 'from' pattern is matched. It can also
+ * apply groups matched from the original pattern using {x} annotation, e.g. {0}.
+ * @param fieldSelectors Collection of regular expressions that are used to match fields. If the
+ * type is matched (using 'from') and the field is matched (or the list of fields selectors is
+ * empty) the field's type gets rewritten according to the 'to' parameter.
+ */
+class RewriteRule(
+        private val from: String,
+        private val to: String,
+        private val fieldSelectors: List<String> = emptyList()) {
+
+    // We escape '$' so we don't conflict with regular expression symbols.
+    private val inputPattern = Pattern.compile("^${from.replace("$", "\\$")}$")
+    private val outputPattern = to.replace("$", "\$")
+
+    private val fields = fieldSelectors.map { Pattern.compile("^$it$") }
+
+    /**
+     * Rewrites the given java type. Returns null if this rule is not applicable for the given type.
+     */
+    fun apply(input: JavaType): JavaType? {
+        if (fields.isNotEmpty()) {
+            return null
+        }
+
+        return applyInternal(input)
+    }
+
+    /**
+     * Rewrites the given field type. Returns null if this rule is not applicable for the given
+     * type.
+     */
+    fun apply(inputField: JavaField) : JavaField? {
+        val typeRewriteResult = applyInternal(inputField.owner) ?: return null
+
+        val isFieldInTheFilter = fields.isEmpty()
+                || fields.any { it.matcher(inputField.name).matches() }
+        if (isFieldInTheFilter) {
+            return inputField.renameOwner(typeRewriteResult)
+        }
+
+        return null
+    }
+
+    private fun applyInternal(input: JavaType): JavaType? {
+        val matcher = inputPattern.matcher(input.fullName)
+        if (!matcher.matches()) {
+            return null
+        }
+
+        var result = outputPattern
+        for (i in 0..matcher.groupCount() - 1) {
+            result = result.replace("{$i}", matcher.group(i + 1))
+        }
+
+        return JavaType(result)
+    }
+
+    override fun toString() : String {
+        return "$inputPattern -> $outputPattern " + fields.joinToString { it.toString() }
+    }
+
+    /** Returns JSON data model of this class */
+    fun toJson() : JsonData {
+        return JsonData(from, to, fieldSelectors)
+    }
+
+
+    /**
+     * JSON data model for [RewriteRule].
+     */
+    data class JsonData(
+            @SerializedName("from")
+            val from: String,
+
+            @SerializedName("to")
+            val to: String,
+
+            @SerializedName("fieldSelectors")
+            val fieldSelectors: List<String>? = null)  {
+
+        /** Creates instance of [RewriteRule] */
+        fun toRule() : RewriteRule {
+            return RewriteRule(from, to, fieldSelectors.orEmpty())
+        }
+    }
+
+}
+
+
diff --git a/jetifier/jetifier/core/src/main/kotlin/android/support/tools/jetifier/core/transform/TransformationContext.kt b/jetifier/jetifier/core/src/main/kotlin/android/support/tools/jetifier/core/transform/TransformationContext.kt
new file mode 100644
index 0000000..3f8af95
--- /dev/null
+++ b/jetifier/jetifier/core/src/main/kotlin/android/support/tools/jetifier/core/transform/TransformationContext.kt
@@ -0,0 +1,85 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *       http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR 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.tools.jetifier.core.transform
+
+import android.support.tools.jetifier.core.config.Config
+import android.support.tools.jetifier.core.rules.JavaType
+import android.support.tools.jetifier.core.transform.proguard.ProGuardType
+import java.util.regex.Pattern
+
+/**
+ * Context to share the transformation state between individual [Transformer]s.
+ */
+class TransformationContext(val config: Config) {
+
+    // Merges all packages prefixes into one regEx pattern
+    private val packagePrefixPattern = Pattern.compile(
+        "^(" + config.restrictToPackagePrefixes.map { "($it)" }.joinToString("|") + ").*$")
+
+    /** Counter for [reportNoMappingFoundFailure] calls. */
+    var mappingNotFoundFailuresCount = 0
+        private set
+
+    /** Counter for [reportNoProGuardMappingFoundFailure] calls. */
+    var proGuardMappingNotFoundFailuresCount = 0
+        private set
+
+    /** Returns whether any errors were found during the transformation process */
+    fun wasErrorFound() = mappingNotFoundFailuresCount > 0
+
+    /**
+     * Returns whether the given type is eligible for rewrite.
+     *
+     * If not, the transformers should ignore it.
+     */
+    fun isEligibleForRewrite(type: JavaType) : Boolean {
+        if (config.restrictToPackagePrefixes.isEmpty()) {
+            return false
+        }
+        return packagePrefixPattern.matcher(type.fullName).matches()
+    }
+
+    /**
+     * Returns whether the given ProGuard type reference is eligible for rewrite.
+     *
+     * Keep in mind that his has limited capabilities - mainly when * is used as a prefix. Rules
+     * like *.v7 are not matched by prefix support.v7. So don't rely on it and use
+     * the [ProGuardTypesMap] as first.
+     */
+    fun isEligibleForRewrite(type: ProGuardType) : Boolean {
+        if (config.restrictToPackagePrefixes.isEmpty()) {
+            return false
+        }
+        return packagePrefixPattern.matcher(type.value).matches()
+    }
+
+    /**
+     * Used to report that there was a reference found that satisfies [isEligibleForRewrite] but no
+     * mapping was found to rewrite it.
+     */
+    fun reportNoMappingFoundFailure() {
+        mappingNotFoundFailuresCount++
+    }
+
+    /**
+     * Used to report that there was a reference found in the ProGuard file that satisfies
+     * [isEligibleForRewrite] but no mapping was found to rewrite it.
+     */
+    fun reportNoProGuardMappingFoundFailure() {
+        proGuardMappingNotFoundFailuresCount++
+    }
+}
\ No newline at end of file
diff --git a/jetifier/jetifier/core/src/main/kotlin/android/support/tools/jetifier/core/transform/Transformer.kt b/jetifier/jetifier/core/src/main/kotlin/android/support/tools/jetifier/core/transform/Transformer.kt
new file mode 100644
index 0000000..0c6c8aa
--- /dev/null
+++ b/jetifier/jetifier/core/src/main/kotlin/android/support/tools/jetifier/core/transform/Transformer.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.support.tools.jetifier.core.transform
+
+import android.support.tools.jetifier.core.archive.ArchiveFile
+
+/**
+ * Interface to be implemented by any class that wants process files.
+ */
+interface Transformer {
+
+    /**
+     * Returns whether this instance can process the given file.
+     */
+    fun canTransform(file: ArchiveFile): Boolean
+
+    /**
+     * Runs transformation of the given file.
+     */
+    fun runTransform(file: ArchiveFile)
+
+}
diff --git a/jetifier/jetifier/core/src/main/kotlin/android/support/tools/jetifier/core/transform/bytecode/ByteCodeTransformer.kt b/jetifier/jetifier/core/src/main/kotlin/android/support/tools/jetifier/core/transform/bytecode/ByteCodeTransformer.kt
new file mode 100644
index 0000000..33235e0
--- /dev/null
+++ b/jetifier/jetifier/core/src/main/kotlin/android/support/tools/jetifier/core/transform/bytecode/ByteCodeTransformer.kt
@@ -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 android.support.tools.jetifier.core.transform.bytecode
+
+import android.support.tools.jetifier.core.archive.ArchiveFile
+import android.support.tools.jetifier.core.config.Config
+import android.support.tools.jetifier.core.transform.TransformationContext
+import android.support.tools.jetifier.core.transform.Transformer
+import org.objectweb.asm.ClassReader
+import org.objectweb.asm.ClassWriter
+
+/**
+ * The [Transformer] responsible for java byte code refactoring.
+ */
+class ByteCodeTransformer internal constructor(context: TransformationContext) : Transformer {
+
+    private val remapper: CoreRemapperImpl = CoreRemapperImpl(context)
+
+
+    override fun canTransform(file: ArchiveFile) = file.isClassFile()
+
+    override fun runTransform(file: ArchiveFile) {
+        val reader = ClassReader(file.data)
+        val writer = ClassWriter(0 /* flags */)
+
+        val visitor = remapper.createClassRemapper(writer)
+
+        reader.accept(visitor, 0 /* flags */)
+
+        file.data = writer.toByteArray()
+    }
+}
\ No newline at end of file
diff --git a/jetifier/jetifier/core/src/main/kotlin/android/support/tools/jetifier/core/transform/bytecode/CoreRemapper.kt b/jetifier/jetifier/core/src/main/kotlin/android/support/tools/jetifier/core/transform/bytecode/CoreRemapper.kt
new file mode 100644
index 0000000..50f3b31
--- /dev/null
+++ b/jetifier/jetifier/core/src/main/kotlin/android/support/tools/jetifier/core/transform/bytecode/CoreRemapper.kt
@@ -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.support.tools.jetifier.core.transform.bytecode
+
+import android.support.tools.jetifier.core.rules.JavaField
+import android.support.tools.jetifier.core.rules.JavaType
+
+/**
+ * High-level re-mapping interface to provide only the refactorings needed by jetifier.
+ */
+interface CoreRemapper {
+    fun rewriteType(type: JavaType) : JavaType
+
+    fun rewriteField(field: JavaField) : JavaField
+}
\ No newline at end of file
diff --git a/jetifier/jetifier/core/src/main/kotlin/android/support/tools/jetifier/core/transform/bytecode/CoreRemapperImpl.kt b/jetifier/jetifier/core/src/main/kotlin/android/support/tools/jetifier/core/transform/bytecode/CoreRemapperImpl.kt
new file mode 100644
index 0000000..486cc25
--- /dev/null
+++ b/jetifier/jetifier/core/src/main/kotlin/android/support/tools/jetifier/core/transform/bytecode/CoreRemapperImpl.kt
@@ -0,0 +1,78 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *       http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR 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.tools.jetifier.core.transform.bytecode
+
+import android.support.tools.jetifier.core.map.TypesMap
+import android.support.tools.jetifier.core.rules.JavaField
+import android.support.tools.jetifier.core.rules.JavaType
+import android.support.tools.jetifier.core.transform.TransformationContext
+import android.support.tools.jetifier.core.transform.bytecode.asm.CustomClassRemapper
+import android.support.tools.jetifier.core.transform.bytecode.asm.CustomRemapper
+import android.support.tools.jetifier.core.utils.Log
+import org.objectweb.asm.ClassVisitor
+
+/**
+ * Applies mappings defined in [TypesMap] during the remapping process.
+ */
+class CoreRemapperImpl(private val context: TransformationContext) : CoreRemapper {
+
+    companion object {
+        const val TAG = "CoreRemapperImpl"
+    }
+
+    private val typesMap = context.config.typesMap
+
+    fun createClassRemapper(visitor: ClassVisitor): CustomClassRemapper {
+        return CustomClassRemapper(visitor, CustomRemapper(this))
+    }
+
+    override fun rewriteType(type: JavaType): JavaType {
+        val result = typesMap.types[type]
+
+        if (!context.isEligibleForRewrite(type)) {
+            return type
+        }
+
+        if (result != null) {
+            Log.i(TAG, "  map: %s -> %s", type, result)
+            return result
+        }
+
+        context.reportNoMappingFoundFailure()
+        Log.e(TAG, "No mapping for: " + type)
+        return type
+    }
+
+    override fun rewriteField(field : JavaField): JavaField {
+        val result = typesMap.fields[field]
+
+        if (!context.isEligibleForRewrite(field.owner)) {
+            return field
+        }
+
+        if (result != null) {
+            Log.i(TAG, "  map: %s -> %s", field, result)
+            return result
+        }
+
+        context.reportNoMappingFoundFailure()
+        Log.e(TAG, "No mapping for: " + field)
+        return field
+    }
+
+}
+
diff --git a/jetifier/jetifier/core/src/main/kotlin/android/support/tools/jetifier/core/transform/bytecode/asm/CustomClassRemapper.kt b/jetifier/jetifier/core/src/main/kotlin/android/support/tools/jetifier/core/transform/bytecode/asm/CustomClassRemapper.kt
new file mode 100644
index 0000000..692e65d
--- /dev/null
+++ b/jetifier/jetifier/core/src/main/kotlin/android/support/tools/jetifier/core/transform/bytecode/asm/CustomClassRemapper.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.support.tools.jetifier.core.transform.bytecode.asm
+
+import org.objectweb.asm.ClassVisitor
+import org.objectweb.asm.FieldVisitor
+import org.objectweb.asm.MethodVisitor
+import org.objectweb.asm.Opcodes
+import org.objectweb.asm.commons.ClassRemapper
+
+/**
+ * Currently only adds field context awareness into [ClassRemapper] and substitutes the default
+ * method remapper with [CustomMethodRemapper]
+ */
+class CustomClassRemapper(cv: ClassVisitor, private val customRemapper: CustomRemapper)
+    : ClassRemapper(Opcodes.ASM5, cv, customRemapper) {
+
+    override fun visitField(access: Int,
+                            name: String,
+                            desc: String?,
+                            signature: String?,
+                            value: Any?) : FieldVisitor? {
+        cv ?: return null
+
+        val field = customRemapper.mapField(className, name)
+        val fieldVisitor = cv.visitField(
+                access,
+                field.name,
+                remapper.mapDesc(desc),
+                remapper.mapSignature(signature, true),
+                remapper.mapValue(value))
+
+        fieldVisitor ?: return null
+
+        return createFieldRemapper(fieldVisitor)
+    }
+
+    override fun createMethodRemapper(mv: MethodVisitor) : MethodVisitor {
+        return CustomMethodRemapper(mv, customRemapper)
+    }
+}
diff --git a/jetifier/jetifier/core/src/main/kotlin/android/support/tools/jetifier/core/transform/bytecode/asm/CustomMethodRemapper.kt b/jetifier/jetifier/core/src/main/kotlin/android/support/tools/jetifier/core/transform/bytecode/asm/CustomMethodRemapper.kt
new file mode 100644
index 0000000..cc60cbf
--- /dev/null
+++ b/jetifier/jetifier/core/src/main/kotlin/android/support/tools/jetifier/core/transform/bytecode/asm/CustomMethodRemapper.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.support.tools.jetifier.core.transform.bytecode.asm
+
+import org.objectweb.asm.MethodVisitor
+import org.objectweb.asm.Opcodes
+import org.objectweb.asm.commons.MethodRemapper
+
+/**
+ * Currently only adds field context awareness into [MethodRemapper]
+ */
+internal class CustomMethodRemapper(mv:MethodVisitor,
+                                    private val customRemapper: CustomRemapper)
+    : MethodRemapper(Opcodes.ASM5, mv, customRemapper) {
+
+    override fun visitFieldInsn(opcode: Int, owner: String, name: String, desc: String?) {
+        mv ?: return
+
+        val field = customRemapper.mapField(owner, name)
+        mv.visitFieldInsn(opcode, field.owner.fullName, field.name, remapper.mapDesc(desc))
+    }
+}
\ No newline at end of file
diff --git a/jetifier/jetifier/core/src/main/kotlin/android/support/tools/jetifier/core/transform/bytecode/asm/CustomRemapper.kt b/jetifier/jetifier/core/src/main/kotlin/android/support/tools/jetifier/core/transform/bytecode/asm/CustomRemapper.kt
new file mode 100644
index 0000000..5debf70
--- /dev/null
+++ b/jetifier/jetifier/core/src/main/kotlin/android/support/tools/jetifier/core/transform/bytecode/asm/CustomRemapper.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.support.tools.jetifier.core.transform.bytecode.asm
+
+import android.support.tools.jetifier.core.rules.JavaField
+import android.support.tools.jetifier.core.rules.JavaType
+import android.support.tools.jetifier.core.transform.bytecode.CoreRemapper
+import org.objectweb.asm.commons.Remapper
+
+/**
+ * Extends [Remapper] with a capability to rewrite field names together with their owner.
+ */
+class CustomRemapper(val remapperImpl: CoreRemapper) : Remapper() {
+
+    override fun map(typeName: String): String {
+        return remapperImpl.rewriteType(JavaType(typeName)).fullName
+    }
+
+    fun mapField(ownerName: String, fieldName: String): JavaField {
+        return remapperImpl.rewriteField(JavaField(ownerName, fieldName))
+    }
+
+    override fun mapFieldName(owner: String?, name: String, desc: String?): String {
+        throw RuntimeException("This should not be called")
+    }
+}
\ No newline at end of file
diff --git a/jetifier/jetifier/core/src/main/kotlin/android/support/tools/jetifier/core/transform/pom/PomDependency.kt b/jetifier/jetifier/core/src/main/kotlin/android/support/tools/jetifier/core/transform/pom/PomDependency.kt
new file mode 100644
index 0000000..1622fd7
--- /dev/null
+++ b/jetifier/jetifier/core/src/main/kotlin/android/support/tools/jetifier/core/transform/pom/PomDependency.kt
@@ -0,0 +1,142 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *       http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR 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.tools.jetifier.core.transform.pom
+
+import com.google.gson.annotations.SerializedName
+import org.jdom2.Document
+import org.jdom2.Element
+
+/**
+ * Represents a '<dependency>' XML node of a POM file.
+ *
+ * See documentation of the content at https://maven.apache.org/pom.html#Dependencies
+ */
+data class PomDependency(
+        @SerializedName("groupId")
+        val groupId: String? = null,
+
+        @SerializedName("artifactId")
+        val artifactId: String? = null,
+
+        @SerializedName("version")
+        var version: String? = null,
+
+        @SerializedName("classifier")
+        val classifier: String? = null,
+
+        @SerializedName("type")
+        val type: String? = null,
+
+        @SerializedName("scope")
+        val scope: String? = null,
+
+        @SerializedName("systemPath")
+        val systemPath: String? = null,
+
+        @SerializedName("optional")
+        val optional: String? = null) {
+
+    companion object {
+
+        /**
+         * Creates a new [PomDependency] from the given XML [Element].
+         */
+        fun fromXmlElement(node: Element, properties: Map<String, String>) : PomDependency {
+            var groupId : String? = null
+            var artifactId : String? = null
+            var version : String? = null
+            var classifier : String? = null
+            var type : String? = null
+            var scope : String? = null
+            var systemPath : String? = null
+            var optional : String? = null
+
+            for (childNode in node.children) {
+                when (childNode.name) {
+                    "groupId" -> groupId = XmlUtils.resolveValue(childNode.value, properties)
+                    "artifactId" -> artifactId = XmlUtils.resolveValue(childNode.value, properties)
+                    "version" -> version = XmlUtils.resolveValue(childNode.value, properties)
+                    "classifier" -> classifier = XmlUtils.resolveValue(childNode.value, properties)
+                    "type" -> type = XmlUtils.resolveValue(childNode.value, properties)
+                    "scope" -> scope = XmlUtils.resolveValue(childNode.value, properties)
+                    "systemPath" -> systemPath = XmlUtils.resolveValue(childNode.value, properties)
+                    "optional" -> optional = XmlUtils.resolveValue(childNode.value, properties)
+                }
+            }
+
+            return PomDependency(
+                    groupId = groupId,
+                    artifactId = artifactId,
+                    version = version,
+                    classifier = classifier,
+                    type = type,
+                    scope = scope,
+                    systemPath = systemPath,
+                    optional = optional)
+        }
+
+    }
+
+    init {
+        if (version != null) {
+            version = version!!.toLowerCase()
+        }
+    }
+
+    /**
+     * Whether this dependency should be skipped from the rewriting process
+     */
+    fun shouldSkipRewrite() : Boolean {
+        return scope != null && scope.toLowerCase() == "test"
+    }
+
+    /**
+     * Returns a new dependency created by taking all the items from the [input] dependency and then
+     * overwriting these with all of its non-null items.
+     */
+    fun rewrite(input: PomDependency) : PomDependency {
+        return PomDependency(
+            groupId = groupId ?: input.groupId,
+            artifactId = artifactId ?: input.artifactId,
+            version = version ?: input.version,
+            classifier = classifier ?: input.classifier,
+            type = type ?: input.type,
+            scope = scope ?: input.scope,
+            systemPath = systemPath ?: input.systemPath,
+            optional = optional ?: input.optional
+        )
+    }
+
+    /**
+     * Transforms the current data into XML '<dependency>' node.
+     */
+    fun toXmlElement(document: Document) : Element {
+        val node = Element("dependency")
+        node.namespace = document.rootElement.namespace
+
+        XmlUtils.addStringNodeToNode(node, "groupId", groupId)
+        XmlUtils.addStringNodeToNode(node, "artifactId", artifactId)
+        XmlUtils.addStringNodeToNode(node, "version", version)
+        XmlUtils.addStringNodeToNode(node, "classifier", classifier)
+        XmlUtils.addStringNodeToNode(node, "type", type)
+        XmlUtils.addStringNodeToNode(node, "scope", scope)
+        XmlUtils.addStringNodeToNode(node, "systemPath", systemPath)
+        XmlUtils.addStringNodeToNode(node, "optional", optional)
+
+        return node
+    }
+}
\ No newline at end of file
diff --git a/jetifier/jetifier/core/src/main/kotlin/android/support/tools/jetifier/core/transform/pom/PomDocument.kt b/jetifier/jetifier/core/src/main/kotlin/android/support/tools/jetifier/core/transform/pom/PomDocument.kt
new file mode 100644
index 0000000..d5bdc3a
--- /dev/null
+++ b/jetifier/jetifier/core/src/main/kotlin/android/support/tools/jetifier/core/transform/pom/PomDocument.kt
@@ -0,0 +1,133 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *       http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR 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.tools.jetifier.core.transform.pom
+
+import android.support.tools.jetifier.core.archive.ArchiveFile
+import android.support.tools.jetifier.core.utils.Log
+import org.jdom2.Document
+import org.jdom2.Element
+
+/**
+ * Wraps a single POM XML [ArchiveFile] with parsed metadata about transformation related sections.
+ */
+class PomDocument(val file: ArchiveFile, private val document: Document) {
+
+    companion object {
+        private const val TAG = "Pom"
+
+        fun loadFrom(file: ArchiveFile) : PomDocument {
+            val document = XmlUtils.createDocumentFromByteArray(file.data)
+            val pomDoc = PomDocument(file, document)
+            pomDoc.initialize()
+            return pomDoc
+        }
+    }
+
+    val dependencies : MutableSet<PomDependency> = mutableSetOf()
+    private val properties : MutableMap<String, String> = mutableMapOf()
+    private var dependenciesGroup : Element? = null
+    private var hasChanged : Boolean = false
+
+    private fun initialize() {
+        val propertiesGroup = document.rootElement
+                .getChild("properties", document.rootElement.namespace)
+        if (propertiesGroup != null) {
+            propertiesGroup.children
+                .filterNot { it.value.isNullOrEmpty() }
+                .forEach { properties[it.name] = it.value }
+        }
+
+        dependenciesGroup = document.rootElement
+                .getChild("dependencies", document.rootElement.namespace) ?: return
+        dependenciesGroup!!.children.mapTo(dependencies) {
+            PomDependency.fromXmlElement(it, properties)
+        }
+    }
+
+    /**
+     * Validates that this document is consistent with the provided [rules].
+     *
+     * Currently it checks that all the dependencies that are going to be rewritten by the given
+     * rules satisfy the minimal version requirements defined by the rules.
+     */
+    fun validate(rules: List<PomRewriteRule>) : Boolean {
+        if (dependenciesGroup == null) {
+            // Nothing to validate as this file has no dependencies section
+            return true
+        }
+
+        return dependencies.all { dep -> rules.all { it.validateVersion(dep) } }
+    }
+
+    /**
+     * Applies the given [rules] to rewrite the POM file.
+     *
+     * Changes are not saved back until requested.
+     */
+    fun applyRules(rules: List<PomRewriteRule>) {
+        if (dependenciesGroup == null) {
+            // Nothing to transform as this file has no dependencies section
+            return
+        }
+
+        val newDependencies = mutableSetOf<PomDependency>()
+        for (dependency in dependencies) {
+            if (dependency.shouldSkipRewrite()) {
+                continue
+            }
+
+            val rule = rules.firstOrNull { it.matches(dependency) }
+            if (rule == null) {
+                // No rule to rewrite => keep it
+                newDependencies.add(dependency)
+            } else {
+                // Replace with new dependencies
+                newDependencies.addAll(rule.to.mapTo(newDependencies){ it.rewrite(dependency) })
+            }
+        }
+
+        if (newDependencies.isEmpty()) {
+            // No changes
+            return
+        }
+
+        dependenciesGroup!!.children.clear()
+        newDependencies.forEach { dependenciesGroup!!.addContent(it.toXmlElement(document)) }
+        hasChanged = true
+    }
+
+    /**
+     * Saves any current pending changes back to the file if needed.
+     */
+    fun saveBackToFileIfNeeded() {
+        if (!hasChanged) {
+            return
+        }
+
+        file.data =  XmlUtils.convertDocumentToByteArray(document)
+    }
+
+    /**
+     * Logs the information about the current file using info level.
+     */
+    fun logDocumentDetails() {
+        Log.i(TAG, "POM file at: '%s'", file.relativePath)
+        for ((groupId, artifactId, version) in dependencies) {
+            Log.i(TAG, "- Dep: %s:%s:%s", groupId, artifactId, version)
+        }
+    }
+}
\ No newline at end of file
diff --git a/jetifier/jetifier/core/src/main/kotlin/android/support/tools/jetifier/core/transform/pom/PomRewriteRule.kt b/jetifier/jetifier/core/src/main/kotlin/android/support/tools/jetifier/core/transform/pom/PomRewriteRule.kt
new file mode 100644
index 0000000..070a640
--- /dev/null
+++ b/jetifier/jetifier/core/src/main/kotlin/android/support/tools/jetifier/core/transform/pom/PomRewriteRule.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.support.tools.jetifier.core.transform.pom
+
+import android.support.tools.jetifier.core.utils.Log
+import com.google.gson.annotations.SerializedName
+
+/**
+ * Rule that defines how to rewrite a dependency element in a POM file.
+ *
+ * Any dependency that is matched against [from] should be rewritten to list of the dependencies
+ * defined in [to].
+ */
+data class PomRewriteRule(val from: PomDependency, val to: List<PomDependency>) {
+
+    companion object {
+        val TAG : String = "PomRule"
+    }
+
+    /**
+     * Validates that the given [input] dependency has a valid version.
+     */
+    fun validateVersion(input: PomDependency, document: PomDocument? = null) : Boolean {
+        if (from.version == null || input.version == null) {
+            return true
+        }
+
+        if (!matches(input)) {
+            return true
+        }
+
+        if (!areVersionsMatching(from.version!!, input.version!!)) {
+            Log.e(TAG, "Version mismatch! Expected version '%s' but found version '%s' for " +
+                    "'%s:%s' in '%s' file.", from.version, input.version, input.groupId,
+                    input.artifactId, document?.file?.relativePath)
+            return false
+        }
+
+        return true
+    }
+
+    /**
+     * Checks if the given [version] is supported to be rewritten with a rule having [ourVersion].
+     *
+     * Version entry can be actually quite complicated, see the full documentation at:
+     * https://maven.apache.org/pom.html#Dependencies
+     */
+    private fun areVersionsMatching(ourVersion: String, version: String) : Boolean {
+        if (version == "latest" || version == "release") {
+            return true
+        }
+
+        if (version.endsWith(",)") || version.endsWith(",]")) {
+            return true
+        }
+
+        if (version.endsWith("$ourVersion]")) {
+            return true
+        }
+
+        return ourVersion == version
+    }
+
+    fun matches(input: PomDependency) : Boolean {
+        return input.artifactId == from.artifactId && input.groupId == from.groupId
+    }
+
+    /** Returns JSON data model of this class */
+    fun toJson() : PomRewriteRule.JsonData {
+        return PomRewriteRule.JsonData(from, to)
+    }
+
+
+    /**
+     * JSON data model for [PomRewriteRule].
+     */
+    data class JsonData(
+            @SerializedName("from")
+            val from: PomDependency,
+            @SerializedName("to")
+            val to: List<PomDependency>)  {
+
+        /** Creates instance of [PomRewriteRule] */
+        fun toRule() : PomRewriteRule {
+            return PomRewriteRule(from, to.filterNotNull())
+        }
+    }
+
+}
\ No newline at end of file
diff --git a/jetifier/jetifier/core/src/main/kotlin/android/support/tools/jetifier/core/transform/pom/PomScanner.kt b/jetifier/jetifier/core/src/main/kotlin/android/support/tools/jetifier/core/transform/pom/PomScanner.kt
new file mode 100644
index 0000000..e9cc511
--- /dev/null
+++ b/jetifier/jetifier/core/src/main/kotlin/android/support/tools/jetifier/core/transform/pom/PomScanner.kt
@@ -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.tools.jetifier.core.transform.pom
+
+import android.support.tools.jetifier.core.archive.Archive
+import android.support.tools.jetifier.core.archive.ArchiveFile
+import android.support.tools.jetifier.core.archive.ArchiveItemVisitor
+import android.support.tools.jetifier.core.config.Config
+import android.support.tools.jetifier.core.utils.Log
+
+/**
+ * Helper to scan [Archive]s to find their POM files.
+ */
+class PomScanner(private val config: Config) {
+
+    companion object {
+        private const val TAG = "PomScanner"
+    }
+
+    private val pomFilesInternal = mutableListOf<PomDocument>()
+
+    private var validationFailuresCount = 0
+
+    val pomFiles : List<PomDocument> = pomFilesInternal
+
+    fun wasErrorFound() = validationFailuresCount > 0
+
+    /**
+     * Scans the given [archive] for a POM file
+     *
+     * @return null if POM file was not found
+     */
+    fun scanArchiveForPomFile(archive: Archive) : PomDocument? {
+        val session = PomScannerSession()
+        archive.accept(session)
+
+        if (session.pomFile == null) {
+            return null
+        }
+        val pomFile = session.pomFile!!
+
+        pomFile.logDocumentDetails()
+
+        if (!pomFile.validate(config.pomRewriteRules)) {
+            Log.e(TAG, "Version mismatch!")
+            validationFailuresCount++
+        }
+
+        pomFilesInternal.add(session.pomFile!!)
+
+        return session.pomFile
+    }
+
+
+    private class PomScannerSession : ArchiveItemVisitor {
+
+        var pomFile : PomDocument? = null
+
+        override fun visit(archive: Archive) {
+            for (archiveItem in archive.files) {
+                if (pomFile != null) {
+                    break
+                }
+                archiveItem.accept(this)
+            }
+        }
+
+        override fun visit(archiveFile: ArchiveFile) {
+            if (archiveFile.isPomFile()) {
+                pomFile = PomDocument.loadFrom(archiveFile)
+            }
+        }
+    }
+}
\ No newline at end of file
diff --git a/jetifier/jetifier/core/src/main/kotlin/android/support/tools/jetifier/core/transform/pom/XmlUtils.kt b/jetifier/jetifier/core/src/main/kotlin/android/support/tools/jetifier/core/transform/pom/XmlUtils.kt
new file mode 100644
index 0000000..67b7a3d
--- /dev/null
+++ b/jetifier/jetifier/core/src/main/kotlin/android/support/tools/jetifier/core/transform/pom/XmlUtils.kt
@@ -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.support.tools.jetifier.core.transform.pom
+
+import android.support.tools.jetifier.core.utils.Log
+import org.jdom2.Document
+import org.jdom2.Element
+import org.jdom2.input.SAXBuilder
+import org.jdom2.output.Format
+import org.jdom2.output.XMLOutputter
+import java.io.ByteArrayOutputStream
+import java.util.regex.Pattern
+
+/**
+ * Utilities for handling XML documents.
+ */
+class XmlUtils {
+
+    companion object {
+
+        private val variablePattern = Pattern.compile("\\$\\{([^}]*)}")
+
+        /** Saves the given [Document] to a new byte array */
+        fun convertDocumentToByteArray(document : Document) : ByteArray {
+            val xmlOutput = XMLOutputter()
+            ByteArrayOutputStream().use {
+                xmlOutput.format = Format.getPrettyFormat()
+                xmlOutput.output(document, it)
+                return it.toByteArray()
+            }
+        }
+
+        /** Creates a new [Document] from the given [ByteArray] */
+        fun createDocumentFromByteArray(data: ByteArray) : Document {
+            val builder = SAXBuilder()
+            data.inputStream().use {
+                return builder.build(it)
+            }
+        }
+
+        /**
+         * Creates a new XML element with the given [id] and text given in [value] and puts it under
+         * the given [parent]. Nothing is created if the [value] argument is null or empty.
+         */
+        fun addStringNodeToNode(parent: Element, id: String, value: String?) {
+            if (value.isNullOrEmpty()) {
+                return
+            }
+
+            val element = Element(id)
+            element.text = value
+            element.namespace = parent.namespace
+            parent.children.add(element)
+        }
+
+
+        fun resolveValue(value: String?, properties: Map<String, String>) : String? {
+            if (value == null) {
+                return null
+            }
+
+            val matcher = variablePattern.matcher(value)
+            if (matcher.matches()) {
+                val variableName = matcher.group(1)
+                val varValue = properties[variableName]
+                if (varValue == null) {
+                    Log.e("TAG", "Failed to resolve variable '%s'", value)
+                    return value
+                }
+                return varValue
+            }
+
+            return value
+        }
+    }
+
+}
\ No newline at end of file
diff --git a/jetifier/jetifier/core/src/main/kotlin/android/support/tools/jetifier/core/transform/proguard/ProGuardClassFilterParser.kt b/jetifier/jetifier/core/src/main/kotlin/android/support/tools/jetifier/core/transform/proguard/ProGuardClassFilterParser.kt
new file mode 100644
index 0000000..c431572
--- /dev/null
+++ b/jetifier/jetifier/core/src/main/kotlin/android/support/tools/jetifier/core/transform/proguard/ProGuardClassFilterParser.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.support.tools.jetifier.core.transform.proguard
+
+import android.support.tools.jetifier.core.transform.proguard.patterns.GroupsReplacer
+import android.support.tools.jetifier.core.transform.proguard.patterns.PatternHelper
+import java.util.regex.Pattern
+
+/**
+ * Parses and rewrites ProGuard rules that contain class filters. See ProGuard documentation
+ * https://www.guardsquare.com/en/proguard/manual/usage#filters
+ */
+class ProGuardClassFilterParser(private val mapper : ProGuardTypesMapper) {
+
+    companion object {
+        private const val RULES = "(adaptclassstrings|dontnote|dontwarn)"
+    }
+
+    val replacer = GroupsReplacer(
+        pattern = PatternHelper.build("^ *-$RULES ⦅[^-]+⦆ *$", Pattern.MULTILINE),
+        groupsMap = listOf(
+            { filter : String -> rewriteClassFilter(filter) }
+        )
+    )
+
+    private fun rewriteClassFilter(classFilter: String) : String {
+        return classFilter
+            .splitToSequence(",")
+            .filterNotNull()
+            .map { it.trim() }
+            .filter { it.isNotEmpty() }
+            .map { replaceTypeInClassFilter(it) }
+            .joinToString(separator = ", ")
+    }
+
+    private fun replaceTypeInClassFilter(type: String) : String {
+        if (!type.startsWith('!')) {
+            return mapper.replaceType(type)
+        }
+
+        val withoutNegation = type.substring(1, type.length)
+        return '!' + mapper.replaceType(withoutNegation)
+    }
+}
\ No newline at end of file
diff --git a/jetifier/jetifier/core/src/main/kotlin/android/support/tools/jetifier/core/transform/proguard/ProGuardClassSpecParser.kt b/jetifier/jetifier/core/src/main/kotlin/android/support/tools/jetifier/core/transform/proguard/ProGuardClassSpecParser.kt
new file mode 100644
index 0000000..933ff08
--- /dev/null
+++ b/jetifier/jetifier/core/src/main/kotlin/android/support/tools/jetifier/core/transform/proguard/ProGuardClassSpecParser.kt
@@ -0,0 +1,138 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *       http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR 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.tools.jetifier.core.transform.proguard
+
+import android.support.tools.jetifier.core.transform.proguard.patterns.GroupsReplacer
+import android.support.tools.jetifier.core.transform.proguard.patterns.PatternHelper
+
+/**
+ * Parses and rewrites ProGuard rules that contain class specification. See ProGuard documentation
+ * https://www.guardsquare.com/en/proguard/manual/usage#classspecification
+ */
+class ProGuardClassSpecParser(private val mapper : ProGuardTypesMapper) {
+
+    companion object {
+        private const val RULES = "(keep[a-z]*|whyareyoukeeping|assumenosideeffects)"
+        private const val RULES_MODIFIERS =
+            "(includedescriptorclasses|allowshrinking|allowoptimization|allowobfuscation)"
+
+        private const val CLASS_NAME = "[\\w.$?*_%]+"
+        private const val CLASS_MODIFIERS = "[!]?(public|final|abstract)"
+        private const val CLASS_TYPES = "[!]?(interface|class|enum)"
+
+        private const val ANNOTATION_TYPE = CLASS_NAME
+
+        private const val FIELD_NAME = "[\\w?*_%]+"
+        private const val FIELD_TYPE = CLASS_NAME
+        private const val FIELD_MODIFIERS =
+            "[!]?(public|private|protected|static|volatile|transient)"
+
+        private const val METHOD_MODIFIERS =
+            "[!]?(public|private|protected|static|synchronized|native|abstract|strictfp)"
+        private const val RETURN_TYPE_NAME = CLASS_NAME
+        private const val METHOD_NAME = "[\\w?*_]+"
+        private const val ARGS = "[^)]*"
+    }
+
+    val replacer = GroupsReplacer(
+        pattern = PatternHelper.build(
+            "-$RULES ($RULES_MODIFIERS )*(@⦅$ANNOTATION_TYPE⦆ )?($CLASS_MODIFIERS )*$CLASS_TYPES " +
+            "⦅$CLASS_NAME⦆( (extends|implements) ⦅$CLASS_NAME⦆)?+ *( *\\{⦅[^}]*⦆\\} *)?+"),
+        groupsMap = listOf(
+            { annotation : String -> mapper.replaceType(annotation) },
+            { className : String -> mapper.replaceType(className) },
+            { className2 : String -> mapper.replaceType(className2) },
+            { bodyGroup : String -> rewriteBodyGroup(bodyGroup) }
+        )
+    )
+
+    private val bodyReplacers = listOf(
+        // [@annotation] [[!]public|private|etc...] <fields>;
+        GroupsReplacer(
+            pattern = PatternHelper.build(
+                "^ *(@⦅$ANNOTATION_TYPE⦆ )?($FIELD_MODIFIERS )*<fields> *$"),
+            groupsMap = listOf(
+                { annotation : String -> mapper.replaceType(annotation) }
+        )),
+
+        // [@annotation] [[!]public|private|etc...] fieldType fieldName;
+        GroupsReplacer(
+            pattern = PatternHelper.build(
+                "^ *(@⦅$ANNOTATION_TYPE⦆ )?($FIELD_MODIFIERS )*(⦅$FIELD_TYPE⦆ $FIELD_NAME) *$"),
+            groupsMap = listOf(
+                { annotation : String -> mapper.replaceType(annotation) },
+                { fieldType : String -> mapper.replaceType(fieldType) }
+        )),
+
+        // [@annotation] [[!]public|private|etc...] <methods>;
+        GroupsReplacer(
+            pattern = PatternHelper.build(
+                "^ *(@⦅$ANNOTATION_TYPE⦆ )?($METHOD_MODIFIERS )*<methods> *$"),
+            groupsMap = listOf(
+                { annotation : String -> mapper.replaceType(annotation) }
+        )),
+
+        // [@annotation] [[!]public|private|etc...] className(argumentType,...));
+        GroupsReplacer(
+            pattern = PatternHelper.build(
+                "^ *(@⦅$ANNOTATION_TYPE⦆ )?($METHOD_MODIFIERS )*⦅$CLASS_NAME⦆ *\\(⦅$ARGS⦆\\) *$"),
+            groupsMap = listOf(
+                { annotation : String -> mapper.replaceType(annotation) },
+                { className : String -> mapper.replaceType(className) },
+                { argsType : String -> mapper.replaceMethodArgs(argsType) }
+            )
+        ),
+
+        // [@annotation] [[!]public|private|etc...] <init>(argumentType,...));
+        GroupsReplacer(
+            pattern = PatternHelper.build(
+                "^ *(@⦅$ANNOTATION_TYPE⦆ )?($METHOD_MODIFIERS )*<init> *\\(⦅$ARGS⦆\\) *$"),
+            groupsMap = listOf(
+                { annotation : String -> mapper.replaceType(annotation) },
+                { argsType : String -> mapper.replaceMethodArgs(argsType) }
+        )),
+
+        // [@annotation] [[!]public|private|etc...] returnType methodName(argumentType,...));
+        GroupsReplacer(
+            pattern = PatternHelper.build("^ *(@⦅$ANNOTATION_TYPE⦆ )?($METHOD_MODIFIERS )*" +
+                "⦅$RETURN_TYPE_NAME⦆ $METHOD_NAME *\\(⦅$ARGS⦆\\) *$"),
+            groupsMap = listOf(
+                { annotation : String -> mapper.replaceType(annotation) },
+                { returnType : String -> mapper.replaceType(returnType) },
+                { argsType : String -> mapper.replaceMethodArgs(argsType) }
+        ))
+    )
+
+    private fun rewriteBodyGroup(bodyGroup: String) : String {
+        if (bodyGroup == "*" || bodyGroup == "**") {
+            return bodyGroup
+        }
+
+        return bodyGroup
+            .split(';')
+            .map {
+                for (replacer in bodyReplacers) {
+                    val matcher = replacer.pattern.matcher(it)
+                    if (matcher.matches()) {
+                        return@map replacer.runReplacements(matcher)
+                    }
+                }
+                return@map it
+            }
+            .joinToString(";")
+    }
+}
\ No newline at end of file
diff --git a/jetifier/jetifier/core/src/main/kotlin/android/support/tools/jetifier/core/transform/proguard/ProGuardTransformer.kt b/jetifier/jetifier/core/src/main/kotlin/android/support/tools/jetifier/core/transform/proguard/ProGuardTransformer.kt
new file mode 100644
index 0000000..423bf05
--- /dev/null
+++ b/jetifier/jetifier/core/src/main/kotlin/android/support/tools/jetifier/core/transform/proguard/ProGuardTransformer.kt
@@ -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
+ */
+
+package android.support.tools.jetifier.core.transform.proguard
+
+import android.support.tools.jetifier.core.archive.ArchiveFile
+import android.support.tools.jetifier.core.transform.TransformationContext
+import android.support.tools.jetifier.core.transform.Transformer
+import android.support.tools.jetifier.core.transform.proguard.patterns.ReplacersRunner
+import java.nio.charset.StandardCharsets
+
+/**
+ * The [Transformer] responsible for ProGuard files refactoring.
+ */
+class ProGuardTransformer internal constructor(context: TransformationContext) : Transformer {
+
+    private val mapper = ProGuardTypesMapper(context)
+
+    val replacer = ReplacersRunner(listOf(
+        ProGuardClassSpecParser(mapper).replacer,
+        ProGuardClassFilterParser(mapper).replacer
+    ))
+
+    override fun canTransform(file: ArchiveFile): Boolean {
+        return file.isProGuardFile()
+    }
+
+    override fun runTransform(file: ArchiveFile) {
+        val sb = StringBuilder(file.data.toString(StandardCharsets.UTF_8))
+        val result = replacer.applyReplacers(sb.toString())
+        file.data = result.toByteArray()
+    }
+}
+
diff --git a/jetifier/jetifier/core/src/main/kotlin/android/support/tools/jetifier/core/transform/proguard/ProGuardType.kt b/jetifier/jetifier/core/src/main/kotlin/android/support/tools/jetifier/core/transform/proguard/ProGuardType.kt
new file mode 100644
index 0000000..be15fbf
--- /dev/null
+++ b/jetifier/jetifier/core/src/main/kotlin/android/support/tools/jetifier/core/transform/proguard/ProGuardType.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.support.tools.jetifier.core.transform.proguard
+
+import android.support.tools.jetifier.core.rules.JavaType
+
+/**
+ * Represents a type reference in ProGuard file. This type is similar to the regular java type but
+ * can also contain wildcards (*,**,?).
+ */
+data class ProGuardType(val value: String) {
+
+    init {
+        if (value.contains('.')) {
+            throw IllegalArgumentException("The type does not support '.' as package separator!")
+        }
+    }
+
+    companion object {
+        /** Creates the type reference from notation where packages are separated using '.' */
+        fun fromDotNotation(type: String) : ProGuardType {
+            return ProGuardType(type.replace('.', '/'))
+        }
+    }
+
+    /**
+     * Whether the type reference is trivial such as "*".
+     */
+    fun isTrivial() = value == "*" || value == "**" || value == "***" || value == "%"
+
+    fun toJavaType() : JavaType? {
+        if (value.contains('*') || value.contains('?')) {
+            return null
+        }
+        return JavaType(value)
+    }
+
+    /** Returns the type reference as a string where packages are separated using '.' */
+    fun toDotNotation() : String {
+        return value.replace('/', '.')
+    }
+}
\ No newline at end of file
diff --git a/jetifier/jetifier/core/src/main/kotlin/android/support/tools/jetifier/core/transform/proguard/ProGuardTypesMap.kt b/jetifier/jetifier/core/src/main/kotlin/android/support/tools/jetifier/core/transform/proguard/ProGuardTypesMap.kt
new file mode 100644
index 0000000..03d6282
--- /dev/null
+++ b/jetifier/jetifier/core/src/main/kotlin/android/support/tools/jetifier/core/transform/proguard/ProGuardTypesMap.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.support.tools.jetifier.core.transform.proguard
+
+/**
+ * Contains custom mappings to map support library types referenced in ProGuard to new ones.
+ */
+data class ProGuardTypesMap(val rules: Map<ProGuardType, ProGuardType>) {
+
+    companion object {
+        val EMPTY = ProGuardTypesMap(emptyMap())
+    }
+
+    /** Returns JSON data model of this class */
+    fun toJson() : JsonData {
+        return JsonData(rules.map { it.key.value to it.value.value }.toMap())
+    }
+
+    /**
+     * JSON data model for [ProGuardTypesMap].
+     */
+    data class JsonData(val rules: Map<String, String>)  {
+
+        /** Creates instance of [ProGuardTypesMap] */
+        fun toMappings() : ProGuardTypesMap {
+            return ProGuardTypesMap(rules.map { ProGuardType(it.key) to ProGuardType(it.value) }.toMap())
+        }
+    }
+}
\ No newline at end of file
diff --git a/jetifier/jetifier/core/src/main/kotlin/android/support/tools/jetifier/core/transform/proguard/ProGuardTypesMapper.kt b/jetifier/jetifier/core/src/main/kotlin/android/support/tools/jetifier/core/transform/proguard/ProGuardTypesMapper.kt
new file mode 100644
index 0000000..28195a3
--- /dev/null
+++ b/jetifier/jetifier/core/src/main/kotlin/android/support/tools/jetifier/core/transform/proguard/ProGuardTypesMapper.kt
@@ -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 android.support.tools.jetifier.core.transform.proguard
+
+import android.support.tools.jetifier.core.transform.TransformationContext
+import android.support.tools.jetifier.core.utils.Log
+
+/**
+ * Maps ProGuard types using [TypesMap] and [ProGuardTypesMap].
+ */
+class ProGuardTypesMapper(private val context: TransformationContext) {
+
+    companion object {
+        const val TAG = "ProGuardTypesMapper"
+    }
+
+    private val config = context.config
+
+    /**
+     * Replaces the given ProGuard type that was parsed from the ProGuard file (thus having '.' as
+     * a separator.
+     */
+    fun replaceType(typeToReplace: String) : String {
+        val type = ProGuardType.fromDotNotation(typeToReplace)
+        if (type.isTrivial()) {
+            return typeToReplace
+        }
+
+        val javaType = type.toJavaType()
+        if (javaType != null) {
+            // We are dealing with an explicit type definition
+            if (!context.isEligibleForRewrite(javaType)) {
+                return typeToReplace
+            }
+
+            val result = config.typesMap.types[javaType]
+            if (result == null) {
+                context.reportNoProGuardMappingFoundFailure()
+                Log.e(TAG, "No mapping for: " + type)
+                return typeToReplace
+            }
+
+            Log.i(TAG, "  map: %s -> %s", type, result)
+            return result.toDotNotation()
+        }
+
+        // Type contains wildcards - try custom rules map
+        val result = config.proGuardMap.rules[type]
+        if (result != null) {
+            Log.i(TAG, "  map: %s -> %s", type, result)
+            return result.toDotNotation()
+        }
+
+        // Report error only when we are sure
+        if (context.isEligibleForRewrite(type)) {
+            context.reportNoProGuardMappingFoundFailure()
+            Log.e(TAG, "No mapping for: " + type)
+        }
+        return typeToReplace
+    }
+
+    /**
+     * Replaces the given arguments list used in a ProGuard method rule. Argument must be separated
+     * with ','. The method also accepts '...' symbol as defined in the spec.
+     */
+    fun replaceMethodArgs(argsTypes: String) : String {
+        if (argsTypes.isEmpty() || argsTypes == "...") {
+            return argsTypes
+        }
+
+        return argsTypes
+            .splitToSequence(",")
+            .filterNotNull()
+            .map { it.trim() }
+            .filter { it.isNotEmpty() }
+            .map { replaceType(it) }
+            .joinToString(separator = ", ")
+    }
+}
\ No newline at end of file
diff --git a/jetifier/jetifier/core/src/main/kotlin/android/support/tools/jetifier/core/transform/proguard/patterns/GroupsReplacer.kt b/jetifier/jetifier/core/src/main/kotlin/android/support/tools/jetifier/core/transform/proguard/patterns/GroupsReplacer.kt
new file mode 100644
index 0000000..6213a55
--- /dev/null
+++ b/jetifier/jetifier/core/src/main/kotlin/android/support/tools/jetifier/core/transform/proguard/patterns/GroupsReplacer.kt
@@ -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.support.tools.jetifier.core.transform.proguard.patterns
+
+import java.util.regex.Matcher
+import java.util.regex.Pattern
+
+/**
+ * Applies replacements on a matched string using the given [pattern] and its groups. Each group is
+ * mapped using a lambda from [groupsMap].
+ */
+class GroupsReplacer(val pattern: Pattern,
+                     private val groupsMap: List<(String) -> String>) {
+
+    /**
+     * Takes the given [matcher] and replace its matched groups using mapping functions given in
+     * [groupsMap].
+     */
+    fun runReplacements(matcher: Matcher) : String {
+        var result = matcher.group(0)
+
+        // We go intentionally backwards to replace using indexes
+        for (i in groupsMap.size - 1 downTo 0) {
+            val groupVal = matcher.group(i + 1) ?: continue
+            val localStart = matcher.start(i + 1) - matcher.start()
+            val localEnd =  matcher.end(i + 1) - matcher.start()
+
+            result = result.replaceRange(
+                startIndex = localStart,
+                endIndex = localEnd,
+                replacement = groupsMap[i].invoke(groupVal))
+        }
+        return result
+    }
+
+}
\ No newline at end of file
diff --git a/jetifier/jetifier/core/src/main/kotlin/android/support/tools/jetifier/core/transform/proguard/patterns/PatternHelper.kt b/jetifier/jetifier/core/src/main/kotlin/android/support/tools/jetifier/core/transform/proguard/patterns/PatternHelper.kt
new file mode 100644
index 0000000..3171185
--- /dev/null
+++ b/jetifier/jetifier/core/src/main/kotlin/android/support/tools/jetifier/core/transform/proguard/patterns/PatternHelper.kt
@@ -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.tools.jetifier.core.transform.proguard.patterns
+
+import java.util.regex.Pattern
+
+/**
+ * Helps to build regular expression [Pattern]s defined with less verbose syntax.
+ *
+ * You can use following shortcuts:
+ * '⦅⦆' - denotes a capturing group (normally '()' is capturing group)
+ * '()' - denotes non-capturing group (normally (?:) is non-capturing group)
+ * ' ' - denotes a whitespace characters (at least one)
+ * ' *' - denotes a whitespace characters (any)
+ * ';' - denotes ' *;'
+ */
+object PatternHelper {
+
+    private val rewrites = listOf(
+        " *" to "[\\s]*", // Optional space
+        " " to "[\\s]+", // Space
+        "⦅" to "(", // Capturing group start
+        "⦆" to ")", // Capturing group end
+        ";" to "[\\s]*;" // Allow spaces in front of ';'
+    )
+
+    /**
+     * Transforms the given [toReplace] according to the rules defined in documentation of this
+     * class and compiles it to a [Pattern].
+     */
+    fun build(toReplace: String, flags : Int = 0) : Pattern {
+        var result = toReplace
+        result = result.replace("(?<!\\\\)\\(".toRegex(), "(?:")
+        rewrites.forEach { result = result.replace(it.first, it.second) }
+        return Pattern.compile(result, flags)
+    }
+}
\ No newline at end of file
diff --git a/jetifier/jetifier/core/src/main/kotlin/android/support/tools/jetifier/core/transform/proguard/patterns/ReplacersRunner.kt b/jetifier/jetifier/core/src/main/kotlin/android/support/tools/jetifier/core/transform/proguard/patterns/ReplacersRunner.kt
new file mode 100644
index 0000000..54501f9
--- /dev/null
+++ b/jetifier/jetifier/core/src/main/kotlin/android/support/tools/jetifier/core/transform/proguard/patterns/ReplacersRunner.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.support.tools.jetifier.core.transform.proguard.patterns
+
+/**
+ * Runs multiple [GroupsReplacer]s on given strings.
+ */
+class ReplacersRunner(val replacers: List<GroupsReplacer>) {
+
+    /**
+     * Runs all the [GroupsReplacer]s on the given [input].
+     *
+     * The replacers have to be distinct as this method can't guarantee that output of one replacer
+     * won't be matched by another replacer.
+     */
+    fun applyReplacers(input : String) : String {
+        val sb = StringBuilder()
+        var lastSeenChar = 0
+        var processedInput = input
+
+        for (replacer in replacers) {
+            val matcher = replacer.pattern.matcher(processedInput)
+
+            while (matcher.find()) {
+                if (lastSeenChar < matcher.start()) {
+                    sb.append(input, lastSeenChar, matcher.start())
+                }
+
+                val result = replacer.runReplacements(matcher)
+                sb.append(result)
+                lastSeenChar = matcher.end()
+            }
+
+            if (lastSeenChar == 0) {
+                continue
+            }
+
+            if (lastSeenChar <= processedInput.length - 1) {
+                sb.append(processedInput, lastSeenChar, processedInput.length)
+            }
+
+            lastSeenChar = 0
+            processedInput = sb.toString()
+            sb.setLength(0)
+        }
+
+        return processedInput
+    }
+
+}
\ No newline at end of file
diff --git a/jetifier/jetifier/core/src/main/kotlin/android/support/tools/jetifier/core/transform/resource/XmlResourcesTransformer.kt b/jetifier/jetifier/core/src/main/kotlin/android/support/tools/jetifier/core/transform/resource/XmlResourcesTransformer.kt
new file mode 100644
index 0000000..0a29828
--- /dev/null
+++ b/jetifier/jetifier/core/src/main/kotlin/android/support/tools/jetifier/core/transform/resource/XmlResourcesTransformer.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.support.tools.jetifier.core.transform.resource
+
+import android.support.tools.jetifier.core.archive.ArchiveFile
+import android.support.tools.jetifier.core.map.TypesMap
+import android.support.tools.jetifier.core.rules.JavaTypeXmlRef
+import android.support.tools.jetifier.core.transform.TransformationContext
+import android.support.tools.jetifier.core.transform.Transformer
+import android.support.tools.jetifier.core.utils.Log
+import java.nio.charset.Charset
+import java.nio.charset.StandardCharsets
+import java.util.regex.Pattern
+import javax.xml.stream.XMLInputFactory
+
+/**
+ * Transformer for XML resource files.
+ *
+ * Searches for any java type reference that is pointing to the support library and rewrites it
+ * using the available mappings from [TypesMap].
+ */
+class XmlResourcesTransformer internal constructor(private val context: TransformationContext)
+        : Transformer {
+
+    companion object {
+        const val TAG = "XmlResourcesTransformer"
+
+        const val PATTERN_TYPE_GROUP = 1
+    }
+
+    /**
+     * List of regular expression patterns used to find support library references in XML files.
+     *
+     * Matches xml tags in form of:
+     * 1. '<(/)prefix(SOMETHING)'.
+     * 2. <view ... class="prefix(SOMETHING)" ...>
+     *
+     * Note that this can also rewrite commented blocks of XML. But on a library level we don't care
+     * much about comments.
+     */
+    private val patterns = listOf(
+        Pattern.compile("</?([a-zA-Z0-9.]+)"),
+        Pattern.compile("<view[^>]*class=\"([a-zA-Z0-9.\$_]+)\"[^>]*>")
+    )
+
+    private val typesMap = context.config.typesMap
+
+    override fun canTransform(file: ArchiveFile) = file.isXmlFile() && !file.isPomFile()
+
+    override fun runTransform(file: ArchiveFile) {
+        file.data = transform(file.data)
+    }
+
+    fun transform(data: ByteArray) : ByteArray {
+        var changesDone = false
+
+        val charset = getCharset(data)
+        val sb = StringBuilder(data.toString(charset))
+        for (pattern in patterns) {
+            var matcher = pattern.matcher(sb)
+            while (matcher.find()) {
+                val typeToReplace = JavaTypeXmlRef(matcher.group(PATTERN_TYPE_GROUP))
+                val result = rewriteType(typeToReplace)
+                if (result == typeToReplace) {
+                    continue
+                }
+                sb.replace(matcher.start(PATTERN_TYPE_GROUP), matcher.end(PATTERN_TYPE_GROUP),
+                    result.fullName)
+                changesDone = true
+                matcher = pattern.matcher(sb)
+            }
+        }
+
+        if (changesDone) {
+            return sb.toString().toByteArray(charset)
+        }
+
+        return data
+    }
+
+    fun getCharset(data: ByteArray) : Charset {
+        data.inputStream().use {
+            val xmlReader = XMLInputFactory.newInstance().createXMLStreamReader(it)
+
+            xmlReader.encoding ?: return StandardCharsets.UTF_8 // Encoding was not detected
+
+            val result = Charset.forName(xmlReader.encoding)
+            if (result == null) {
+                Log.e(TAG, "Failed to find charset for encoding '%s'", xmlReader.encoding)
+                return StandardCharsets.UTF_8
+            }
+            return result
+        }
+    }
+
+    fun rewriteType(type: JavaTypeXmlRef): JavaTypeXmlRef {
+        val javaType = type.toJavaType()
+        if (!context.isEligibleForRewrite(javaType)) {
+            return type
+        }
+
+        val result = typesMap.types[javaType]
+        if (result != null) {
+            Log.i(TAG, "  map: %s -> %s", type, result)
+            return JavaTypeXmlRef(result)
+        }
+
+        context.reportNoMappingFoundFailure()
+        Log.e(TAG, "No mapping for: " + type)
+        return type
+    }
+}
\ No newline at end of file
diff --git a/jetifier/jetifier/core/src/main/kotlin/android/support/tools/jetifier/core/utils/Log.kt b/jetifier/jetifier/core/src/main/kotlin/android/support/tools/jetifier/core/utils/Log.kt
new file mode 100644
index 0000000..902dea4
--- /dev/null
+++ b/jetifier/jetifier/core/src/main/kotlin/android/support/tools/jetifier/core/utils/Log.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.support.tools.jetifier.core.utils
+
+object Log {
+
+    var currentLevel : LogLevel = LogLevel.INFO
+
+    var logConsumer : LogConsumer = StdOutLogConsumer()
+
+    fun setLevel(level: String?) {
+        currentLevel = when (level) {
+            "debug" -> LogLevel.DEBUG
+            "verbose" -> LogLevel.VERBOSE
+            else -> LogLevel.INFO
+        }
+
+    }
+
+    fun e(tag: String, message: String, vararg args: Any?) {
+        if (currentLevel >= LogLevel.ERROR) {
+            logConsumer.error("[$tag] $message".format(*args))
+        }
+    }
+
+    fun d(tag: String, message: String, vararg args: Any?) {
+        if (currentLevel >= LogLevel.DEBUG) {
+            logConsumer.debug("[$tag] $message".format(*args))
+        }
+    }
+
+    fun i(tag: String, message: String, vararg args: Any?) {
+        if (currentLevel >= LogLevel.INFO) {
+            logConsumer.info("[$tag] $message".format(*args))
+        }
+    }
+
+    fun v(tag: String, message: String, vararg args: Any?) {
+        if (currentLevel >= LogLevel.VERBOSE) {
+            logConsumer.verbose("[$tag] $message".format(*args))
+        }
+    }
+
+}
\ No newline at end of file
diff --git a/jetifier/jetifier/core/src/main/kotlin/android/support/tools/jetifier/core/utils/LogConsumer.kt b/jetifier/jetifier/core/src/main/kotlin/android/support/tools/jetifier/core/utils/LogConsumer.kt
new file mode 100644
index 0000000..ddebd25
--- /dev/null
+++ b/jetifier/jetifier/core/src/main/kotlin/android/support/tools/jetifier/core/utils/LogConsumer.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.support.tools.jetifier.core.utils
+
+/**
+ * Interface to plug custom logs consumers to [Log].
+ */
+interface LogConsumer {
+
+    fun error(message: String)
+
+    fun info(message: String)
+
+    fun verbose(message: String)
+
+    fun debug(message: String)
+
+}
+
diff --git a/jetifier/jetifier/core/src/main/kotlin/android/support/tools/jetifier/core/utils/LogLevel.kt b/jetifier/jetifier/core/src/main/kotlin/android/support/tools/jetifier/core/utils/LogLevel.kt
new file mode 100644
index 0000000..f46b8f6
--- /dev/null
+++ b/jetifier/jetifier/core/src/main/kotlin/android/support/tools/jetifier/core/utils/LogLevel.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.support.tools.jetifier.core.utils
+
+enum class LogLevel(val priority : Int) {
+    ERROR(0),
+    INFO(1),
+    VERBOSE(2),
+    DEBUG(3),
+}
\ No newline at end of file
diff --git a/jetifier/jetifier/core/src/main/kotlin/android/support/tools/jetifier/core/utils/StdOutLogConsumer.kt b/jetifier/jetifier/core/src/main/kotlin/android/support/tools/jetifier/core/utils/StdOutLogConsumer.kt
new file mode 100644
index 0000000..7cfd25e
--- /dev/null
+++ b/jetifier/jetifier/core/src/main/kotlin/android/support/tools/jetifier/core/utils/StdOutLogConsumer.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.support.tools.jetifier.core.utils
+
+/**
+ * Prints logs to the standard output.
+ */
+class StdOutLogConsumer : LogConsumer {
+
+    override fun error(message: String) {
+        println("ERROR: $message")
+    }
+
+    override fun info(message: String) {
+        println("INFO: $message")
+    }
+
+    override fun verbose(message: String) {
+        println("VERBOSE: $message")
+    }
+
+    override fun debug(message: String) {
+        println("DEBUG: $message")
+    }
+}
\ No newline at end of file
diff --git a/jetifier/jetifier/core/src/main/resources/default.config b/jetifier/jetifier/core/src/main/resources/default.config
new file mode 100644
index 0000000..27b2374
--- /dev/null
+++ b/jetifier/jetifier/core/src/main/resources/default.config
@@ -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
+
+{
+    # Skip packages that don't match the following regex
+    restrictToPackagePrefixes: [
+        "android/support/"
+    ],
+    rules: [
+        # preferences.v7
+        {
+            # Take any field starting with 'dialog' prefix in R file and move it to dialogs.R
+            from: "android/support/v7/preferences/R$(.*)",
+            to: "android/jetpack/prefs/dialogs/R${0}",
+            fieldSelectors: ["dialog_(.*)"]
+        },
+        {
+            from: "android/support/v7/preferences/DialogPreference",
+            to: "android/jetpack/prefs/dialogs/DialogPreference"
+        },
+        {
+            from: "android/support/v7/preferences/(.*)",
+            to: "android/jetpack/prefs/main/{0}"
+        },
+        # preferences.v14
+        {
+            from: "android/support/v14/preferences/(.*)",
+            to: "android/jetpack/prefs/main/{0}"
+        },
+    ]
+}
\ No newline at end of file
diff --git a/jetifier/jetifier/core/src/test/kotlin/android/support/tools/jetifier/core/config/ConfigParserTest.kt b/jetifier/jetifier/core/src/test/kotlin/android/support/tools/jetifier/core/config/ConfigParserTest.kt
new file mode 100644
index 0000000..4a03ef3
--- /dev/null
+++ b/jetifier/jetifier/core/src/test/kotlin/android/support/tools/jetifier/core/config/ConfigParserTest.kt
@@ -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 android.support.tools.jetifier.core.config
+
+import com.google.common.truth.Truth
+import org.junit.Test
+
+class ConfigParserTest {
+
+    @Test fun parseConfig_validInput() {
+        val confStr =
+                "{\n" +
+                "    restrictToPackagePrefixes: [\"android/support/\"],\n" +
+                "    # Sample comment \n" +
+                "    rules: [\n" +
+                "        {\n" +
+                "            from: \"android/support/v14/preferences/(.*)\",\n" +
+                "            to: \"android/jetpack/prefs/main/{0}\"\n" +
+                "        },\n" +
+                "        {\n" +
+                "            from: \"android/support/v14/preferences/(.*)\",\n" +
+                "            to: \"android/jetpack/prefs/main/{0}\",\n" +
+                "            fieldSelectors: [\"dialog_(.*)\"]\n" +
+                "        }\n" +
+                "    ],\n" +
+                "    pomRules: [\n" +
+                "        {\n" +
+                "            from: {groupId: \"g\", artifactId: \"a\", version: \"1.0\"},\n" +
+                "            to: [\n" +
+                "                {groupId: \"g\", artifactId: \"a\", version: \"2.0\"} \n" +
+                "            ]\n" +
+                "        }\n" +
+                "    ]\n" +
+                "}"
+
+        val config = ConfigParser.parseFromString(confStr)
+
+        Truth.assertThat(config).isNotNull()
+        Truth.assertThat(config!!.restrictToPackagePrefixes[0]).isEqualTo("android/support/")
+        Truth.assertThat(config.rewriteRules.size).isEqualTo(2)
+    }
+}
+
diff --git a/jetifier/jetifier/core/src/test/kotlin/android/support/tools/jetifier/core/map/MapGenerationTest.kt b/jetifier/jetifier/core/src/test/kotlin/android/support/tools/jetifier/core/map/MapGenerationTest.kt
new file mode 100644
index 0000000..e7f8570
--- /dev/null
+++ b/jetifier/jetifier/core/src/test/kotlin/android/support/tools/jetifier/core/map/MapGenerationTest.kt
@@ -0,0 +1,334 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *       http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR 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.tools.jetifier.core.map
+
+import android.support.tools.jetifier.core.config.Config
+import android.support.tools.jetifier.core.rules.JavaField
+import android.support.tools.jetifier.core.rules.JavaType
+import android.support.tools.jetifier.core.rules.RewriteRule
+import android.support.tools.jetifier.core.transform.proguard.ProGuardTypesMap
+import com.google.common.truth.Truth
+import org.junit.Test
+
+
+class MapGenerationTest {
+
+    @Test fun fromOneType_toOneType() {
+        ScanTester
+            .testThatRules(
+                RewriteRule("android/support/v7/(.*)", "android/test/{0}")
+            )
+            .withAllowedPrefixes(
+                "android/support/"
+            )
+            .forGivenTypes(
+                JavaType("android/support/v7/pref/Preference")
+            )
+            .mapInto(
+                types = mapOf(
+                    "android/support/v7/pref/Preference" to "android/test/pref/Preference"
+                ),
+                fields = mapOf(
+                )
+            )
+            .andIsComplete()
+    }
+
+    @Test fun fromTwoTypes_toOneType_prefixRespected() {
+        ScanTester
+            .testThatRules(
+                RewriteRule("android/support/v7/(.*)", "android/test/{0}"),
+                RewriteRule("android/support/v14/(.*)", "android/test/{0}")
+            )
+            .withAllowedPrefixes(
+                "android/support/v7/"
+            )
+            .forGivenTypes(
+                JavaType("android/support/v7/pref/Preference"),
+                JavaType("android/support/v14/pref/PreferenceDialog")
+            )
+            .mapInto(
+                types = mapOf(
+                    "android/support/v7/pref/Preference" to "android/test/pref/Preference"
+                ),
+                fields = mapOf(
+                )
+            )
+            .andIsComplete()
+    }
+
+    @Test fun fromTwoTypes_toTwoTypes_distinctRules() {
+        ScanTester
+            .testThatRules(
+                RewriteRule("android/support/v7/(.*)", "android/test/{0}"),
+                RewriteRule("android/support/v14/(.*)", "android/test/{0}")
+            )
+            .withAllowedPrefixes(
+                "android/support/v7/",
+                "android/support/v14/"
+            )
+            .forGivenTypes(
+                JavaType("android/support/v7/pref/Preference"),
+                JavaType("android/support/v14/pref/PreferenceDialog")
+            )
+            .mapInto(
+                types = mapOf(
+                    "android/support/v7/pref/Preference" to "android/test/pref/Preference",
+                    "android/support/v14/pref/PreferenceDialog" to "android/test/pref/PreferenceDialog"
+                ),
+                fields = mapOf(
+                )
+            )
+            .andIsComplete()
+    }
+
+    @Test fun fromTwoTypes_toTwoTypes_respectsOrder() {
+        ScanTester
+            .testThatRules(
+                RewriteRule("android/support/v14/(.*)", "android/test/{0}"),
+                RewriteRule("android/support/(.*)", "android/fallback/{0}")
+            )
+            .withAllowedPrefixes(
+                "android/support/"
+            )
+            .forGivenTypes(
+                JavaType("android/support/v7/pref/Preference"),
+                JavaType("android/support/v14/pref/PreferenceDialog")
+            )
+            .mapInto(
+                types = mapOf(
+                    "android/support/v7/pref/Preference" to "android/fallback/v7/pref/Preference",
+                    "android/support/v14/pref/PreferenceDialog" to "android/test/pref/PreferenceDialog"
+                ),
+                fields = mapOf(
+                )
+            )
+            .andIsComplete()
+    }
+
+    @Test fun mapTwoFields_usingOneTypeRule() {
+        ScanTester
+            .testThatRules(
+                RewriteRule("android/support/v7/(.*)", "android/test/{0}")
+            )
+            .withAllowedPrefixes(
+                "android/support/"
+            )
+            .forGivenFields(
+                JavaField("android/support/v7/pref/Preference", "count"),
+                JavaField("android/support/v7/pref/Preference", "min")
+            )
+            .mapInto(
+                types = mapOf(
+                ),
+                fields = mapOf(
+                    "android/support/v7/pref/Preference" to mapOf(
+                        "android/test/pref/Preference" to listOf(
+                            "count",
+                            "min"
+                        )
+                    )
+                )
+            )
+            .andIsComplete()
+    }
+
+    @Test fun mapFieldInInnerClass_usingOneTypeRule() {
+        ScanTester
+            .testThatRules(
+                RewriteRule("android/support/v7/(.*)", "android/test/{0}")
+            )
+            .withAllowedPrefixes(
+                "android/support/"
+            )
+            .forGivenFields(
+                JavaField("android/support/v7/pref/R\$attr", "border")
+            )
+            .mapInto(
+                types = mapOf(
+                ),
+                fields = mapOf(
+                    "android/support/v7/pref/R\$attr" to mapOf(
+                        "android/test/pref/R\$attr" to listOf(
+                            "border"
+                        )
+                    )
+                )
+            )
+            .andIsComplete()
+    }
+
+    @Test fun mapPrivateFields_shouldIgnore() {
+        ScanTester
+            .testThatRules(
+                RewriteRule("android/support/v7/(.*)", "android/test/{0}")
+            )
+            .withAllowedPrefixes(
+                "android/support/"
+            )
+            .forGivenFields(
+                JavaField("android/support/v7/pref/Preference", "mCount"),
+                JavaField("android/support/v7/pref/Preference", "this$0")
+            )
+            .mapInto(
+                types = mapOf(
+                ),
+                fields = mapOf(
+                )
+            )
+            .andIsComplete()
+    }
+
+    @Test fun mapType_usingFieldSelector_shouldNotApply() {
+        ScanTester
+            .testThatRules(
+                RewriteRule("android/support/v7/(.*)", "android/test/{0}", listOf("count"))
+            )
+            .withAllowedPrefixes(
+                "android/support/"
+            )
+            .forGivenTypes(
+                JavaType("android/support/v7/pref/Preference")
+            )
+            .mapInto(
+                types = mapOf(
+                    "android/support/v7/pref/Preference" to "android/support/v7/pref/Preference"
+                ),
+                fields = mapOf(
+                )
+            )
+            .andIsNotComplete()
+    }
+
+    @Test fun mapField_noApplicableRule() {
+        ScanTester
+            .testThatRules(
+                RewriteRule("android/support/v7/(.*)", "android/test/{0}", listOf("count2"))
+            )
+            .withAllowedPrefixes(
+                "android/support/"
+            )
+            .forGivenFields(
+                JavaField("android/support/v7/pref/Preference", "count")
+            )
+            .mapInto(
+                types = mapOf(
+                ),
+                fields = mapOf(
+                    "android/support/v7/pref/Preference" to mapOf(
+                        "android/support/v7/pref/Preference" to listOf(
+                            "count"
+                        )
+                    )
+                )
+            )
+            .andIsNotComplete()
+    }
+
+    @Test fun mapTwoFields_usingTwoFieldsSelectors() {
+        ScanTester
+            .testThatRules(
+                RewriteRule("android/support/v7/(.*)", "android/test/{0}", listOf("count")),
+                RewriteRule("android/support/v7/(.*)", "android/test2/{0}", listOf("size"))
+            )
+            .withAllowedPrefixes(
+                "android/support/"
+            )
+            .forGivenFields(
+                JavaField("android/support/v7/pref/Preference", "count"),
+                JavaField("android/support/v7/pref/Preference", "size")
+            )
+            .mapInto(
+                types = mapOf(
+                ),
+                fields = mapOf(
+                    "android/support/v7/pref/Preference" to mapOf(
+                        "android/test/pref/Preference" to listOf(
+                            "count"
+                        ),
+                        "android/test2/pref/Preference" to listOf(
+                            "size"
+                        )
+                    )
+                )
+            )
+            .andIsComplete()
+    }
+
+
+    object ScanTester {
+
+        fun testThatRules(vararg rules: RewriteRule) = Step1(rules.toList())
+
+
+        class Step1(val rules: List<RewriteRule>) {
+
+            fun withAllowedPrefixes(vararg prefixes: String) = Step2(rules, prefixes.toList())
+
+
+            class Step2(val rules: List<RewriteRule>, val prefixes: List<String>) {
+
+                private val allTypes: MutableList<JavaType> = mutableListOf()
+                private val allFields: MutableList<JavaField> = mutableListOf()
+                private var wasMapIncomplete = false
+
+
+                fun forGivenTypes(vararg types: JavaType) : Step2 {
+                    allTypes.addAll(types)
+                    return this
+                }
+
+                fun forGivenFields(vararg fields: JavaField) : Step2 {
+                    allFields.addAll(fields)
+                    return this
+                }
+
+                fun mapInto(types: Map<String, String>,
+                            fields: Map<String, Map<String, List<String>>>) : Step2 {
+                    val config = Config(
+                        restrictToPackagePrefixes = prefixes,
+                        rewriteRules = rules,
+                        pomRewriteRules = emptyList(),
+                        typesMap = TypesMap.EMPTY,
+                        proGuardMap = ProGuardTypesMap.EMPTY)
+                    val scanner = MapGeneratorRemapper(config)
+
+                    allTypes.forEach { scanner.rewriteType(it) }
+                    allFields.forEach { scanner.rewriteField(it) }
+
+                    val typesMap = scanner.createTypesMap().toJson()
+                    wasMapIncomplete = scanner.isMapNotComplete
+
+                    Truth.assertThat(typesMap.types).containsExactlyEntriesIn(types)
+                    Truth.assertThat(typesMap.fields).containsExactlyEntriesIn(fields)
+                    return this
+                }
+
+                fun andIsNotComplete() {
+                    Truth.assertThat(wasMapIncomplete).isTrue()
+                }
+
+                fun andIsComplete() {
+                    Truth.assertThat(wasMapIncomplete).isFalse()
+                }
+            }
+
+        }
+
+    }
+}
+
diff --git a/jetifier/jetifier/core/src/test/kotlin/android/support/tools/jetifier/core/transform/RewriteRuleTest.kt b/jetifier/jetifier/core/src/test/kotlin/android/support/tools/jetifier/core/transform/RewriteRuleTest.kt
new file mode 100644
index 0000000..ca6288d
--- /dev/null
+++ b/jetifier/jetifier/core/src/test/kotlin/android/support/tools/jetifier/core/transform/RewriteRuleTest.kt
@@ -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.support.tools.jetifier.core.transform
+
+import android.support.tools.jetifier.core.rules.JavaField
+import android.support.tools.jetifier.core.rules.JavaType
+import android.support.tools.jetifier.core.rules.RewriteRule
+import com.google.common.truth.Truth
+import org.junit.Test
+
+
+class RewriteRuleTest {
+
+    @Test fun noRegEx_shouldRewrite() {
+        RuleTester
+            .testThatRule("A/B", "A/C")
+            .rewritesType("A/B")
+            .into("A/C")
+    }
+
+    @Test fun noRegEx_underscore_shouldRewrite() {
+        RuleTester
+            .testThatRule("A/B_B", "A/C")
+            .rewritesType("A/B_B")
+            .into("A/C")
+    }
+
+    @Test fun groupRegEx_shouldRewrite() {
+        RuleTester
+            .testThatRule("A/B/(.*)", "A/{0}")
+            .rewritesType("A/B/C/D")
+            .into("A/C/D")
+    }
+
+    @Test fun groupRegEx__innerClass_shouldRewrite() {
+        RuleTester
+            .testThatRule("A/B/(.*)", "A/{0}")
+            .rewritesType("A/B/C\$D")
+            .into("A/C\$D")
+    }
+
+    @Test fun fieldRule_noRegEx_shouldRewrite() {
+        RuleTester
+            .testThatRule("A/B", "A/C")
+            .withFieldSelector("MyField")
+            .rewritesField("A/B", "MyField")
+            .into("A/C", "MyField")
+    }
+
+    @Test fun fieldRule_innerClass_groupRegEx_shouldRewrite() {
+        RuleTester
+            .testThatRule("A/B$(.*)", "A/C\${0}")
+            .rewritesType("A/B\$D")
+            .into("A/C\$D")
+    }
+
+    @Test fun noFieldRule_shouldRewriteEvenWithField() {
+        RuleTester
+            .testThatRule("A/B", "A/C")
+            .rewritesField("A/B", "test")
+            .into("A/C", "test")
+    }
+
+
+    object RuleTester {
+
+        fun testThatRule(from: String, to: String) = RuleTesterStep1(from, to)
+
+        class RuleTesterStep1(val from: String, val to: String) {
+
+            val fieldSelectors: MutableList<String> = mutableListOf()
+
+            fun withFieldSelector(input: String) : RuleTesterStep1 {
+                fieldSelectors.add(input)
+                return this
+            }
+
+            fun rewritesField(inputType: String, inputField: String)
+                    = RuleTesterFinalFieldStep(from, to, inputType, inputField, fieldSelectors)
+
+            fun rewritesType(inputType: String)
+                    = RuleTesterFinalTypeStep(from, to, inputType, fieldSelectors)
+        }
+
+        class RuleTesterFinalFieldStep(val fromType: String,
+                                       val toType: String,
+                                       val inputType: String,
+                                       val inputField: String,
+                                       val fieldSelectors: List<String>) {
+
+            fun into(expectedTypeName: String, expectedFieldName: String) {
+                val fieldRule = RewriteRule(fromType, toType, fieldSelectors)
+                val result = fieldRule.apply(JavaField(inputType, inputField))
+                Truth.assertThat(result).isNotNull()
+
+                Truth.assertThat(result!!.owner.fullName).isEqualTo(expectedTypeName)
+                Truth.assertThat(result.name).isEqualTo(expectedFieldName)
+            }
+
+        }
+
+        class RuleTesterFinalTypeStep(val fromType: String,
+                                      val toType: String,
+                                      val inputType: String,
+                                      val fieldSelectors: List<String>) {
+
+            fun into(expectedResult: String) {
+                val fieldRule = RewriteRule(fromType, toType, fieldSelectors)
+                val result = fieldRule.apply(JavaType(inputType))
+                Truth.assertThat(result).isNotNull()
+
+                Truth.assertThat(result).isNotNull()
+                Truth.assertThat(result!!.fullName).isEqualTo(expectedResult)
+            }
+
+        }
+    }
+
+}
+
diff --git a/jetifier/jetifier/core/src/test/kotlin/android/support/tools/jetifier/core/transform/pom/PomDocumentTest.kt b/jetifier/jetifier/core/src/test/kotlin/android/support/tools/jetifier/core/transform/pom/PomDocumentTest.kt
new file mode 100644
index 0000000..d55687f
--- /dev/null
+++ b/jetifier/jetifier/core/src/test/kotlin/android/support/tools/jetifier/core/transform/pom/PomDocumentTest.kt
@@ -0,0 +1,426 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *       http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR 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.tools.jetifier.core.transform.pom
+
+import android.support.tools.jetifier.core.archive.ArchiveFile
+import com.google.common.truth.Truth
+import org.junit.Test
+import java.nio.charset.StandardCharsets
+import java.nio.file.Paths
+
+class PomDocumentTest {
+
+    @Test fun pom_noRules_noChange() {
+        testRewriteToTheSame(
+            givenAndExpectedXml =
+            "  <dependencies>\n" +
+            "    <dependency>\n" +
+            "      <groupId>supportGroup</groupId>\n" +
+            "      <artifactId>supportArtifact</artifactId>\n" +
+            "      <version>4.0</version>\n" +
+            "      <type>jar</type>\n" +
+            "      <scope>test</scope>\n" +
+            "      <optional>true</optional>\n" +
+            "    </dependency>\n" +
+            "  </dependencies>",
+            rules = listOf()
+        )
+    }
+
+    @Test fun pom_oneRule_shouldApply() {
+        testRewrite(
+            givenXml =
+            "  <dependencies>\n" +
+            "    <dependency>\n" +
+            "      <groupId>supportGroup</groupId>\n" +
+            "      <artifactId>supportArtifact</artifactId>\n" +
+            "      <version>4.0</version>\n" +
+            "    </dependency>\n" +
+            "    <dependency>\n" +
+            "      <systemPath>test/test</systemPath>\n" +
+            "    </dependency>\n" +
+            "  </dependencies>",
+            expectedXml =
+            "  <dependencies>\n" +
+            "    <dependency>\n" +
+            "      <groupId>testGroup</groupId>\n" +
+            "      <artifactId>testArtifact</artifactId>\n" +
+            "      <version>1.0</version>\n" +
+            "    </dependency>\n" +
+            "    <dependency>\n" +
+            "      <systemPath>test/test</systemPath>\n" +
+            "    </dependency>\n" +
+            "  </dependencies>",
+            rules = listOf(
+                PomRewriteRule(
+                    PomDependency(
+                        groupId = "supportGroup", artifactId = "supportArtifact",
+                        version =  "4.0"),
+                    listOf(
+                        PomDependency(
+                            groupId = "testGroup", artifactId = "testArtifact",
+                            version = "1.0")
+                    )
+                )
+            )
+        )
+    }
+
+    @Test fun pom_oneRule_shouldSkipTestScopedRule() {
+        testRewriteToTheSame(
+            givenAndExpectedXml =
+            "  <dependencies>\n" +
+            "    <dependency>\n" +
+            "      <groupId>supportGroup</groupId>\n" +
+            "      <artifactId>supportArtifact</artifactId>\n" +
+            "      <version>4.0</version>\n" +
+            "      <scope>test</scope>\n" +
+            "    </dependency>\n" +
+            "  </dependencies>",
+            rules = listOf(
+                PomRewriteRule(
+                    PomDependency(
+                        groupId = "supportGroup", artifactId = "supportArtifact",
+                        version =  "4.0"),
+                    listOf(
+                        PomDependency(
+                            groupId = "testGroup", artifactId = "testArtifact",
+                            version = "1.0")
+                    )
+                )
+            )
+        )
+    }
+
+    @Test fun pom_oneRule_notApplicable() {
+        testRewriteToTheSame(
+            givenAndExpectedXml =
+            "  <dependencies>\n" +
+            "    <dependency>\n" +
+            "      <groupId>supportGroup</groupId>\n" +
+            "      <artifactId>supportArtifact</artifactId>\n" +
+            "      <version>4.0</version>\n" +
+            "    </dependency>\n" +
+            "  </dependencies>",
+            rules = listOf(
+                PomRewriteRule(
+                    PomDependency(
+                        groupId = "supportGroup", artifactId = "supportArtifact2",
+                        version =  "4.0"),
+                    listOf(
+                        PomDependency(
+                            groupId = "testGroup", artifactId = "testArtifact",
+                            version = "1.0")
+                    )
+                )
+            )
+        )
+    }
+
+    @Test fun pom_oneRule_appliedForEachType() {
+        testRewrite(
+            givenXml =
+            "  <dependencies>\n" +
+            "    <dependency>\n" +
+            "      <groupId>supportGroup</groupId>\n" +
+            "      <artifactId>supportArtifact</artifactId>\n" +
+            "      <version>4.0</version>\n" +
+            "      <type>test</type>\n" +
+            "    </dependency>\n" +
+            "    <dependency>\n" +
+            "      <groupId>supportGroup</groupId>\n" +
+            "      <artifactId>supportArtifact</artifactId>\n" +
+            "      <version>4.0</version>\n" +
+            "      <type>compile</type>\n" +
+            "    </dependency>\n" +
+            "  </dependencies>",
+            expectedXml =
+            "  <dependencies>\n" +
+            "    <dependency>\n" +
+            "      <groupId>testGroup</groupId>\n" +
+            "      <artifactId>testArtifact</artifactId>\n" +
+            "      <version>1.0</version>\n" +
+            "      <type>test</type>\n" +
+            "    </dependency>\n" +
+            "    <dependency>\n" +
+            "      <groupId>testGroup</groupId>\n" +
+            "      <artifactId>testArtifact</artifactId>\n" +
+            "      <version>1.0</version>\n" +
+            "      <type>compile</type>\n" +
+            "    </dependency>\n" +
+            "  </dependencies>",
+            rules = listOf(
+                PomRewriteRule(
+                    PomDependency(
+                        groupId = "supportGroup", artifactId = "supportArtifact",
+                        version =  "4.0"),
+                    listOf(
+                        PomDependency(
+                            groupId = "testGroup", artifactId = "testArtifact",
+                            version = "1.0")
+                    )
+                )
+            )
+        )
+    }
+
+    @Test fun pom_multipleTargets_shouldApplyAll() {
+        testRewrite(
+            givenXml =
+            "  <dependencies>\n" +
+            "    <dependency>\n" +
+            "      <groupId>supportGroup</groupId>\n" +
+            "      <artifactId>supportArtifact</artifactId>\n" +
+            "      <version>4.0</version>\n" +
+            "    </dependency>\n" +
+            "  </dependencies>",
+            expectedXml =
+            "  <dependencies>\n" +
+            "    <dependency>\n" +
+            "      <groupId>testGroup</groupId>\n" +
+            "      <artifactId>testArtifact</artifactId>\n" +
+            "      <version>1.0</version>\n" +
+            "    </dependency>\n" +
+            "    <dependency>\n" +
+            "      <groupId>testGroup2</groupId>\n" +
+            "      <artifactId>testArtifact2</artifactId>\n" +
+            "      <version>2.0</version>\n" +
+            "    </dependency>\n" +
+            "  </dependencies>",
+            rules = listOf(
+                PomRewriteRule(
+                    PomDependency(
+                        groupId = "supportGroup", artifactId = "supportArtifact",
+                        version =  "4.0"),
+                    listOf(
+                        PomDependency(
+                            groupId = "testGroup", artifactId = "testArtifact",
+                            version = "1.0"),
+                        PomDependency(
+                            groupId = "testGroup2", artifactId = "testArtifact2",
+                            version = "2.0"))
+                )
+            )
+        )
+    }
+
+    @Test fun pom_multipleRulesAndTargets_shouldApplyAll_distinct() {
+        testRewrite(
+            givenXml =
+            "  <dependencies>\n" +
+            "    <dependency>\n" +
+            "      <groupId>supportGroup</groupId>\n" +
+            "      <artifactId>supportArtifact</artifactId>\n" +
+            "      <version>4.0</version>\n" +
+            "    </dependency>\n" +
+            "    <dependency>\n" +
+            "      <groupId>supportGroup</groupId>\n" +
+            "      <artifactId>supportArtifact2</artifactId>\n" +
+            "      <version>4.0</version>\n" +
+            "    </dependency>\n" +
+            "  </dependencies>",
+            expectedXml =
+            "  <dependencies>\n" +
+            "    <dependency>\n" +
+            "      <groupId>testGroup</groupId>\n" +
+            "      <artifactId>testArtifact</artifactId>\n" +
+            "      <version>1.0</version>\n" +
+            "    </dependency>\n" +
+            "    <dependency>\n" +
+            "      <groupId>testGroup2</groupId>\n" +
+            "      <artifactId>testArtifact2</artifactId>\n" +
+            "      <version>2.0</version>\n" +
+            "    </dependency>\n" +
+            "  </dependencies>",
+            rules = listOf(
+                PomRewriteRule(
+                    PomDependency(
+                        groupId = "supportGroup", artifactId = "supportArtifact",
+                        version =  "4.0"),
+                    listOf(
+                        PomDependency(
+                            groupId = "testGroup", artifactId = "testArtifact",
+                            version = "1.0"),
+                        PomDependency(
+                            groupId = "testGroup2", artifactId = "testArtifact2",
+                            version = "2.0")
+                    )
+                ),
+                PomRewriteRule(
+                    PomDependency(
+                        groupId = "supportGroup", artifactId = "supportArtifact2",
+                        version =  "4.0"),
+                    listOf(
+                        PomDependency(
+                            groupId = "testGroup", artifactId = "testArtifact",
+                            version = "1.0"),
+                        PomDependency(
+                            groupId = "testGroup2", artifactId = "testArtifact2",
+                            version = "2.0"))
+                )
+            )
+        )
+    }
+
+    @Test fun pom_oneRule_hasToKeepExtraAttributesAndRewrite() {
+        testRewrite(
+            givenXml =
+            "  <dependencies>\n" +
+            "    <dependency>\n" +
+            "      <groupId>supportGroup</groupId>\n" +
+            "      <artifactId>supportArtifact</artifactId>\n" +
+            "      <version>4.0</version>\n" +
+            "      <classifier>hey</classifier>\n" +
+            "      <type>jar</type>\n" +
+            "      <scope>runtime</scope>\n" +
+            "      <systemPath>somePath</systemPath>\n" +
+            "      <optional>true</optional>\n" +
+            "    </dependency>\n" +
+            "  </dependencies>",
+            expectedXml =
+            "  <dependencies>\n" +
+            "    <dependency>\n" +
+            "      <groupId>testGroup</groupId>\n" +
+            "      <artifactId>testArtifact</artifactId>\n" +
+            "      <version>1.0</version>\n" +
+            "      <classifier>hey</classifier>\n" +
+            "      <type>jar</type>\n" +
+            "      <scope>runtime</scope>\n" +
+            "      <systemPath>somePath</systemPath>\n" +
+            "      <optional>true</optional>\n" +
+            "    </dependency>\n" +
+            "  </dependencies>",
+            rules = listOf(
+                PomRewriteRule(
+                    PomDependency(
+                        groupId = "supportGroup", artifactId = "supportArtifact",
+                        version =  "4.0"),
+                    listOf(
+                        PomDependency(
+                            groupId = "testGroup", artifactId = "testArtifact",
+                            version = "1.0")
+                    )
+                )
+            )
+        )
+    }
+
+    @Test fun pom_usingEmptyProperties_shouldNotCrash() {
+        val document = loadDocument(
+            "  <properties/>\n" +
+            "  <dependencies>\n" +
+            "    <dependency>\n" +
+            "      <groupId>supportGroup</groupId>\n" +
+            "      <artifactId>\${groupId.version.property}</artifactId>\n" +
+            "      <version>\${groupId.version.property}</version>\n" +
+            "    </dependency>\n" +
+            "  </dependencies>"
+        )
+
+        Truth.assertThat(document.dependencies).hasSize(1)
+    }
+
+    @Test fun pom_usingProperties_shouldResolve() {
+        val document = loadDocument(
+            "  <properties>\n" +
+            "    <groupId.version.property>1.0.0</groupId.version.property>\n" +
+            "    <groupId.artifactId.property>supportArtifact</groupId.artifactId.property>\n" +
+            "  </properties>\n" +
+            "  <dependencies>\n" +
+            "    <dependency>\n" +
+            "      <groupId>supportGroup</groupId>\n" +
+            "      <artifactId>\${groupId.artifactId.property}</artifactId>\n" +
+            "      <version>\${groupId.version.property}</version>\n" +
+            "    </dependency>\n" +
+            "  </dependencies>"
+        )
+
+        Truth.assertThat(document.dependencies).hasSize(1)
+
+        val dependency = document.dependencies.first()
+        Truth.assertThat(dependency.version).isEqualTo("1.0.0")
+        Truth.assertThat(dependency.artifactId).isEqualTo("supportArtifact")
+    }
+
+
+    private fun testRewriteToTheSame(givenAndExpectedXml: String, rules: List<PomRewriteRule>) {
+        testRewrite(givenAndExpectedXml, givenAndExpectedXml, rules)
+    }
+
+    private fun testRewrite(givenXml: String, expectedXml : String, rules: List<PomRewriteRule>) {
+        val given =
+            "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n" +
+            "<project xmlns=\"http://maven.apache.org/POM/4.0.0\" " +
+                "xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\" " +
+                "xsi:schemaLocation=\"http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd\">\n" +
+            "  <!-- Some comment -->\n" +
+            "  <groupId>test.group</groupId>\n" +
+            "  <artifactId>test.artifact.id</artifactId>\n" +
+            "  <version>1.0</version>\n" +
+            "  $givenXml\n" +
+            "</project>\n"
+
+        var expected =
+            "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n" +
+            "<project xmlns=\"http://maven.apache.org/POM/4.0.0\" " +
+                "xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\" " +
+                "xsi:schemaLocation=\"http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd\">\n" +
+            "  <!-- Some comment -->\n" +
+            "  <groupId>test.group</groupId>\n" +
+            "  <artifactId>test.artifact.id</artifactId>\n" +
+            "  <version>1.0</version>\n" +
+            "  $expectedXml\n" +
+            "</project>\n"
+
+        val file = ArchiveFile(Paths.get("pom.xml"), given.toByteArray())
+        val pomDocument = PomDocument.loadFrom(file)
+        pomDocument.applyRules(rules)
+        pomDocument.saveBackToFileIfNeeded()
+        var strResult = file.data.toString(StandardCharsets.UTF_8)
+
+        // Remove spaces in front of '<' and the back of '>'
+        expected = expected.replace(">[ ]+".toRegex(), ">")
+        expected = expected.replace("[ ]+<".toRegex(), "<")
+
+        strResult = strResult.replace(">[ ]+".toRegex(), ">")
+        strResult = strResult.replace("[ ]+<".toRegex(), "<")
+
+        // Replace newline characters to match the ones we are using in the expected string
+        strResult = strResult.replace("\\r\\n".toRegex(), "\n")
+
+        Truth.assertThat(strResult).isEqualTo(expected)
+    }
+
+    private fun loadDocument(givenXml : String) : PomDocument {
+        val given =
+            "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n" +
+            "<project xmlns=\"http://maven.apache.org/POM/4.0.0\" " +
+            "xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\" " +
+            "xsi:schemaLocation=\"http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd\">\n" +
+            "  <!-- Some comment -->\n" +
+            "  <groupId>test.group</groupId>\n" +
+            "  <artifactId>test.artifact.id</artifactId>\n" +
+            "  <version>1.0</version>\n" +
+            "  $givenXml\n" +
+            "</project>\n"
+
+        val file = ArchiveFile(Paths.get("pom.xml"), given.toByteArray())
+        val pomDocument = PomDocument.loadFrom(file)
+        return pomDocument
+    }
+}
+
diff --git a/jetifier/jetifier/core/src/test/kotlin/android/support/tools/jetifier/core/transform/pom/PomRewriteRuleTest.kt b/jetifier/jetifier/core/src/test/kotlin/android/support/tools/jetifier/core/transform/pom/PomRewriteRuleTest.kt
new file mode 100644
index 0000000..34ebd04
--- /dev/null
+++ b/jetifier/jetifier/core/src/test/kotlin/android/support/tools/jetifier/core/transform/pom/PomRewriteRuleTest.kt
@@ -0,0 +1,140 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *       http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR 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.tools.jetifier.core.transform.pom
+
+import com.google.common.truth.Truth
+import org.junit.Test
+
+class PomRewriteRuleTest {
+
+    @Test fun versions_nullInRule_match() {
+        testVersionsMatch(
+            ruleVersion = null,
+            pomVersion = "27.0.0"
+        )
+    }
+
+    @Test fun versions_nullInPom_match() {
+        testVersionsMatch(
+            ruleVersion = "27.0.0",
+            pomVersion = null
+        )
+    }
+
+    @Test fun versions_nullBoth_match() {
+        testVersionsMatch(
+            ruleVersion = null,
+            pomVersion = null
+        )
+    }
+
+    @Test fun versions_same_match() {
+        testVersionsMatch(
+            ruleVersion = "27.0.0",
+            pomVersion = "27.0.0"
+        )
+    }
+
+    @Test fun versions_same_strict_match() {
+        testVersionsMatch(
+            ruleVersion = "27.0.0",
+            pomVersion = "[27.0.0]"
+        )
+    }
+
+    @Test fun versions_different_noMatch() {
+        testVersionsDoNotMatch(
+            ruleVersion = "27.0.0",
+            pomVersion = "26.0.0"
+        )
+    }
+
+    @Test fun versions_release_match() {
+        testVersionsMatch(
+            ruleVersion = "27.0.0",
+            pomVersion = "release"
+        )
+    }
+
+    @Test fun versions_latest_match() {
+        testVersionsMatch(
+            ruleVersion = "27.0.0",
+            pomVersion = "latest"
+        )
+    }
+
+    @Test fun versions_range_rightOpen_match() {
+        testVersionsMatch(
+            ruleVersion = "27.0.0",
+            pomVersion = "(26.0.0,]"
+        )
+    }
+
+    @Test fun versions_range_rightOpen2_match() {
+        testVersionsMatch(
+            ruleVersion = "27.0.0",
+            pomVersion = "(26.0.0,)"
+        )
+    }
+
+    @Test fun versions_range_inclusive_match() {
+        testVersionsMatch(
+            ruleVersion = "27.0.0",
+            pomVersion = "[21.0.0,27.0.0]"
+        )
+    }
+
+    @Test fun versions_range_inclusive_noMatch() {
+        testVersionsDoNotMatch(
+            ruleVersion = "27.0.0",
+            pomVersion = "[21.0.0,26.0.0]"
+        )
+    }
+
+    @Test fun versions_range_exclusive_noMatch() {
+        testVersionsDoNotMatch(
+            ruleVersion = "27.0.0",
+            pomVersion = "[21.0.0,27.0.0)"
+        )
+    }
+
+    @Test fun versions_exclusionRange_match() {
+        testVersionsMatch(
+            ruleVersion = "27.0.0",
+            pomVersion = "(,26.0.0),(26.0.0,)"
+        )
+    }
+
+    private fun testVersionsMatch(ruleVersion: String?, pomVersion: String?) {
+        val from = PomDependency(version = ruleVersion)
+        val pom = PomDependency(version = pomVersion)
+
+        val rule = PomRewriteRule(from, listOf(from))
+
+        Truth.assertThat(rule.validateVersion(pom)).isTrue()
+    }
+
+    private fun testVersionsDoNotMatch(ruleVersion: String?, pomVersion: String?) {
+        val from = PomDependency(version = ruleVersion)
+        val pom = PomDependency(version = pomVersion)
+
+        val rule = PomRewriteRule(from, listOf(from))
+
+        Truth.assertThat(rule.validateVersion(pom)).isFalse()
+    }
+
+}
\ No newline at end of file
diff --git a/jetifier/jetifier/core/src/test/kotlin/android/support/tools/jetifier/core/transform/proguard/ClassFilterTest.kt b/jetifier/jetifier/core/src/test/kotlin/android/support/tools/jetifier/core/transform/proguard/ClassFilterTest.kt
new file mode 100644
index 0000000..2c7d7e2
--- /dev/null
+++ b/jetifier/jetifier/core/src/test/kotlin/android/support/tools/jetifier/core/transform/proguard/ClassFilterTest.kt
@@ -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 android.support.tools.jetifier.core.transform.proguard
+
+import org.junit.Test
+
+class ClassFilterTest {
+
+    @Test fun proGuard_classFilter() {
+        ProGuardTester
+            .forGivenPrefixes(
+                "support/"
+            )
+            .forGivenTypesMap(
+                "support/Activity" to "test/Activity",
+                "support/Fragment" to "test/Fragment"
+            )
+            .testThatGivenProGuard(
+                "-adaptclassstrings support.Activity, support.Fragment, keep.Me"
+            )
+            .rewritesTo(
+                "-adaptclassstrings test.Activity, test.Fragment, keep.Me"
+            )
+    }
+
+    @Test fun proGuard_classFilter_newLineIgnored() {
+        ProGuardTester
+            .forGivenPrefixes(
+                "support/"
+            )
+            .forGivenTypesMap(
+                "support/Activity" to "test/Activity",
+                "support/Fragment" to "test/Fragment"
+            )
+            .testThatGivenProGuard(
+                "-adaptclassstrings support.Activity, support.Fragment, keep.Me \n" +
+                " support.Activity"
+            )
+            .rewritesTo(
+                "-adaptclassstrings test.Activity, test.Fragment, keep.Me \n" +
+                " support.Activity"
+            )
+    }
+
+    @Test fun proGuard_classFilter_spacesRespected() {
+        ProGuardTester
+            .forGivenPrefixes(
+                "support/"
+            )
+            .forGivenTypesMap(
+                "support/Activity" to "test/Activity",
+                "support/Fragment" to "test/Fragment"
+            )
+            .testThatGivenProGuard(
+                "  -adaptclassstrings  support.Activity ,  support.Fragment,keep.Me  "
+            )
+            .rewritesTo(
+                "  -adaptclassstrings  test.Activity, test.Fragment, keep.Me"
+            )
+    }
+
+    @Test fun proGuard_classFilter_negation() {
+        ProGuardTester
+            .forGivenPrefixes(
+                "support/"
+            )
+            .forGivenTypesMap(
+                "support/Activity" to "test/Activity",
+                "support/Fragment" to "test/Fragment"
+            )
+            .testThatGivenProGuard(
+                "  -adaptclassstrings !support.Activity, !support.Fragment, !keep.Me  "
+            )
+            .rewritesTo(
+                "  -adaptclassstrings !test.Activity, !test.Fragment, !keep.Me"
+            )
+    }
+}
\ No newline at end of file
diff --git a/jetifier/jetifier/core/src/test/kotlin/android/support/tools/jetifier/core/transform/proguard/ClassSpecTest.kt b/jetifier/jetifier/core/src/test/kotlin/android/support/tools/jetifier/core/transform/proguard/ClassSpecTest.kt
new file mode 100644
index 0000000..e64590f
--- /dev/null
+++ b/jetifier/jetifier/core/src/test/kotlin/android/support/tools/jetifier/core/transform/proguard/ClassSpecTest.kt
@@ -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.support.tools.jetifier.core.transform.proguard
+
+import org.junit.Test
+
+class ClassSpecTest {
+
+    @Test fun proGuard_classSpec_simple() {
+        ProGuardTester
+            .forGivenPrefixes(
+                "support/"
+            )
+            .forGivenTypesMap(
+                "support/Activity" to "test/Activity",
+                "support/Fragment" to "test/Fragment",
+                "support/Annotation" to "test/Annotation"
+            )
+            .testThatGivenProGuard(
+                "-keep class support.Activity"
+            )
+            .rewritesTo(
+                "-keep class test.Activity"
+            )
+    }
+
+    @Test fun proGuard_classSpec_allExistingRules() {
+        ProGuardTester
+            .forGivenPrefixes(
+                "support/"
+            )
+            .forGivenTypesMap(
+                "support/Activity" to "test/Activity",
+                "support/Fragment" to "test/Fragment",
+                "support/Annotation" to "test/Annotation"
+            )
+            .testThatGivenProGuard(
+                "-keep class support.Activity \n" +
+                "-keepclassmembers class support.Activity \n" +
+                "-keepclasseswithmembers class support.Activity \n" +
+                "-keepnames class support.Activity \n" +
+                "-keepclassmembernames class support.Activity \n" +
+                "-keepclasseswithmembernames class support.Activity \n" +
+                "-whyareyoukeeping class support.Activity \n" +
+                "-assumenosideeffects class support.Activity"
+            )
+            .rewritesTo(
+                "-keep class test.Activity \n" +
+                "-keepclassmembers class test.Activity \n" +
+                "-keepclasseswithmembers class test.Activity \n" +
+                "-keepnames class test.Activity \n" +
+                "-keepclassmembernames class test.Activity \n" +
+                "-keepclasseswithmembernames class test.Activity \n" +
+                "-whyareyoukeeping class test.Activity \n" +
+                "-assumenosideeffects class test.Activity"
+            )
+    }
+
+    @Test fun proGuard_classSpec_rulesModifiers() {
+        ProGuardTester
+            .forGivenPrefixes(
+                "support/"
+            )
+            .forGivenTypesMap(
+                "support/Activity" to "test/Activity",
+                "support/Fragment" to "test/Fragment",
+                "support/Annotation" to "test/Annotation"
+            )
+            .testThatGivenProGuard(
+                "-keep includedescriptorclasses class support.Activity \n" +
+                "-keep allowshrinking class support.Activity \n" +
+                "-keep allowoptimization class support.Activity \n" +
+                "-keep allowobfuscation class support.Activity \n" +
+                "-keep allowshrinking allowoptimization allowobfuscation class support.Activity \n" +
+                "-keep allowshrinking   allowoptimization   allowobfuscation  class support.Activity"
+            )
+            .rewritesTo(
+                "-keep includedescriptorclasses class test.Activity \n" +
+                "-keep allowshrinking class test.Activity \n" +
+                "-keep allowoptimization class test.Activity \n" +
+                "-keep allowobfuscation class test.Activity \n" +
+                "-keep allowshrinking allowoptimization allowobfuscation class test.Activity \n" +
+                "-keep allowshrinking   allowoptimization   allowobfuscation  class test.Activity"
+            )
+    }
+
+    @Test fun proGuard_classSpec_extends() {
+        ProGuardTester
+            .forGivenPrefixes(
+                "support/"
+            )
+            .forGivenTypesMap(
+                "support/Activity" to "test/Activity",
+                "support/Fragment" to "test/Fragment",
+                "support/Annotation" to "test/Annotation"
+            )
+            .testThatGivenProGuard(
+                "-keep class * extends support.Activity \n" +
+                "-keep class support.Fragment extends support.Activity"
+            )
+            .rewritesTo(
+                "-keep class * extends test.Activity \n" +
+                "-keep class test.Fragment extends test.Activity"
+            )
+    }
+
+    @Test fun proGuard_classSpec_modifiers_extends() {
+        ProGuardTester
+            .forGivenPrefixes(
+                "support/"
+            )
+            .forGivenTypesMap(
+                "support/Activity" to "test/Activity"
+            )
+            .testThatGivenProGuard(
+                "-keep !public enum * extends support.Activity \n" +
+                "-keep public !final enum * extends support.Activity"
+            )
+            .rewritesTo(
+                "-keep !public enum * extends test.Activity \n" +
+                "-keep public !final enum * extends test.Activity"
+            )
+    }
+
+    @Test fun proGuard_classSpec_annotation() {
+        ProGuardTester
+            .forGivenPrefixes(
+                "support/"
+            )
+            .forGivenTypesMap(
+                "support/Activity" to "test/Activity",
+                "support/Fragment" to "test/Fragment",
+                "support/Annotation" to "test/Annotation"
+            )
+            .testThatGivenProGuard(
+                "-keep @support.Annotation public class support.Activity \n" +
+                "-keep @some.Annotation public class support.Activity"
+            )
+            .rewritesTo(
+                "-keep @test.Annotation public class test.Activity \n" +
+                "-keep @some.Annotation public class test.Activity"
+            )
+    }
+
+    @Test fun proGuard_classSpec_annotation_extends() {
+        ProGuardTester
+            .forGivenPrefixes(
+                "support/"
+            )
+            .forGivenTypesMap(
+                "support/Activity" to "test/Activity",
+                "support/Fragment" to "test/Fragment",
+                "support/Annotation" to "test/Annotation"
+            )
+            .testThatGivenProGuard(
+                "-keep @support.Annotation public class * extends support.Activity\n" +
+                "-keep @some.Annotation !public class * extends support.Activity"
+            )
+            .rewritesTo(
+                "-keep @test.Annotation public class * extends test.Activity\n" +
+                "-keep @some.Annotation !public class * extends test.Activity"
+            )
+    }
+
+    @Test fun proGuard_classSpec_annotation_extends_spaces() {
+        ProGuardTester
+            .forGivenPrefixes(
+                "support/"
+            )
+            .forGivenTypesMap(
+                "support/Activity" to "test/Activity",
+                "support/Fragment" to "test/Fragment",
+                "support/Annotation" to "test/Annotation"
+            )
+            .testThatGivenProGuard(
+                "-keep \t @support.Annotation \t public  class  *  extends support.Activity"
+            )
+            .rewritesTo(
+                "-keep \t @test.Annotation \t public  class  *  extends test.Activity"
+            )
+    }
+
+}
\ No newline at end of file
diff --git a/jetifier/jetifier/core/src/test/kotlin/android/support/tools/jetifier/core/transform/proguard/ClassSpecTest_FieldTypeSelector.kt b/jetifier/jetifier/core/src/test/kotlin/android/support/tools/jetifier/core/transform/proguard/ClassSpecTest_FieldTypeSelector.kt
new file mode 100644
index 0000000..2832385
--- /dev/null
+++ b/jetifier/jetifier/core/src/test/kotlin/android/support/tools/jetifier/core/transform/proguard/ClassSpecTest_FieldTypeSelector.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.support.tools.jetifier.core.transform.proguard
+
+import org.junit.Test
+
+class ClassSpecTest_FieldTypeSelector {
+
+    @Test fun proGuard_fieldTypeSelector() {
+        ProGuardTester
+            .forGivenPrefixes(
+                "support/"
+            )
+            .forGivenTypesMap(
+                "support/Activity" to "test/Activity",
+                "support/Fragment" to "test/Fragment"
+            )
+            .testThatGivenProGuard(
+                "-keep public class * { \n" +
+                "  support.Activity height; \n" +
+                "  support.Fragment *; \n" +
+                "  keep.Me width; \n" +
+                "}"
+            )
+            .rewritesTo(
+                "-keep public class * { \n" +
+                "  test.Activity height; \n" +
+                "  test.Fragment *; \n" +
+                "  keep.Me width; \n" +
+                "}"
+            )
+    }
+
+    @Test fun proGuard_fieldTypeSelector_modifiers() {
+        ProGuardTester
+            .forGivenPrefixes(
+                "support/"
+            )
+            .forGivenTypesMap(
+                "support/Activity" to "test/Activity",
+                "support/Fragment" to "test/Fragment"
+            )
+            .testThatGivenProGuard(
+                "-keep public class * { \n" +
+                "  public support.Fragment height; \n" +
+                "  !public !static support.Fragment height; \n" +
+                "  !protected support.Fragment height; \n" +
+                "}"
+            )
+            .rewritesTo(
+                "-keep public class * { \n" +
+                "  public test.Fragment height; \n" +
+                "  !public !static test.Fragment height; \n" +
+                "  !protected test.Fragment height; \n" +
+                "}"
+            )
+    }
+
+    @Test fun proGuard_fieldTypeSelector_annotation() {
+        ProGuardTester
+            .forGivenPrefixes(
+                "support/"
+            )
+            .forGivenTypesMap(
+                "support/Activity" to "test/Activity",
+                "support/Fragment" to "test/Fragment",
+                "support/Annotation" to "test/Annotation"
+            )
+            .testThatGivenProGuard(
+                "-keep public class * { \n" +
+                "  @support.Annotation support.Fragment height; \n" +
+                "  @some.Annotation support.Fragment height; \n" +
+                "}"
+            )
+            .rewritesTo(
+                "-keep public class * { \n" +
+                "  @test.Annotation test.Fragment height; \n" +
+                "  @some.Annotation test.Fragment height; \n" +
+                "}"
+            )
+    }
+
+    @Test fun proGuard_fieldTypeSelector_modifiers_annotation() {
+        ProGuardTester
+            .forGivenPrefixes(
+                "support/"
+            )
+            .forGivenTypesMap(
+                "support/Activity" to "test/Activity",
+                "support/Fragment" to "test/Fragment",
+                "support/Annotation" to "test/Annotation"
+            )
+            .testThatGivenProGuard(
+                "-keep public class * { \n" +
+                "  @support.Annotation public support.Fragment height; \n" +
+                "  @support.Annotation !public !static support.Fragment height; \n" +
+                "  @support.Annotation !protected volatile support.Fragment height; \n" +
+                "}"
+            )
+            .rewritesTo(
+                "-keep public class * { \n" +
+                "  @test.Annotation public test.Fragment height; \n" +
+                "  @test.Annotation !public !static test.Fragment height; \n" +
+                "  @test.Annotation !protected volatile test.Fragment height; \n" +
+                "}"
+            )
+    }
+
+    @Test fun proGuard_fieldTypeSelector_modifiers_annotation_spaces() {
+        ProGuardTester
+            .forGivenPrefixes(
+                "support/"
+            )
+            .forGivenTypesMap(
+                "support/Activity" to "test/Activity",
+                "support/Fragment" to "test/Fragment",
+                "support/Annotation" to "test/Annotation"
+            )
+            .testThatGivenProGuard(
+                "-keep public class * { \n" +
+                "  @support.Annotation  public  static \t support.Fragment  height ; \n" +
+                "}"
+            )
+            .rewritesTo(
+                "-keep public class * { \n" +
+                "  @test.Annotation  public  static \t test.Fragment  height ; \n" +
+                "}"
+            )
+    }
+
+}
\ No newline at end of file
diff --git a/jetifier/jetifier/core/src/test/kotlin/android/support/tools/jetifier/core/transform/proguard/ClassSpecTest_FieldsSelector.kt b/jetifier/jetifier/core/src/test/kotlin/android/support/tools/jetifier/core/transform/proguard/ClassSpecTest_FieldsSelector.kt
new file mode 100644
index 0000000..6f6a1f9
--- /dev/null
+++ b/jetifier/jetifier/core/src/test/kotlin/android/support/tools/jetifier/core/transform/proguard/ClassSpecTest_FieldsSelector.kt
@@ -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.support.tools.jetifier.core.transform.proguard
+
+import org.junit.Test
+
+class ClassSpecTest_FieldsSelector {
+
+    @Test fun proGuard_fieldsSelector_minimal() {
+        ProGuardTester
+            .forGivenPrefixes(
+                "support/"
+            )
+            .forGivenTypesMap(
+                "support/Activity" to "test/Activity"
+            )
+            .testThatGivenProGuard(
+                "-keep public class * extends support.Activity { \n" +
+                "  <fields>; \n" +
+                "}"
+            )
+            .rewritesTo(
+                "-keep public class * extends test.Activity { \n" +
+                "  <fields>; \n" +
+                "}"
+            )
+    }
+
+    @Test fun proGuard_fieldsSelector_modifiers() {
+        ProGuardTester
+            .forGivenPrefixes(
+            )
+            .forGivenTypesMap(
+            )
+            .testThatGivenProGuard(
+                "-keep public class * { \n" +
+                "  public <fields>; \n" +
+                "  public static <fields>; \n" +
+                "  !private !protected <fields>; \n" +
+                "}"
+            )
+            .rewritesTo(
+                "-keep public class * { \n" +
+                "  public <fields>; \n" +
+                "  public static <fields>; \n" +
+                "  !private !protected <fields>; \n" +
+                "}"
+            )
+    }
+
+    @Test fun proGuard_fieldsSelector_modifiers_annotation() {
+        ProGuardTester
+            .forGivenPrefixes(
+                "support/"
+            )
+            .forGivenTypesMap(
+                "support/Annotation" to "test/Annotation"
+            )
+            .testThatGivenProGuard(
+                "-keep public class * { \n" +
+                "  @support.Annotation public <fields>; \n" +
+                "  @support.Annotation public static <fields>; \n" +
+                "  @support.Annotation !private !protected <fields>; \n" +
+                "}"
+            )
+            .rewritesTo(
+                "-keep public class * { \n" +
+                "  @test.Annotation public <fields>; \n" +
+                "  @test.Annotation public static <fields>; \n" +
+                "  @test.Annotation !private !protected <fields>; \n" +
+                "}"
+            )
+    }
+
+    @Test fun proGuard_fieldsSelector_modifiers_annotation_spaces() {
+        ProGuardTester
+            .forGivenPrefixes(
+                "support/"
+            )
+            .forGivenTypesMap(
+                "support/Annotation" to "test/Annotation"
+            )
+            .testThatGivenProGuard(
+                "-keep public class * { \n" +
+                "  @support.Annotation  public \t  <fields> ; \n" +
+                "}"
+            )
+            .rewritesTo(
+                "-keep public class * { \n" +
+                "  @test.Annotation  public \t  <fields> ; \n" +
+                "}"
+            )
+    }
+}
\ No newline at end of file
diff --git a/jetifier/jetifier/core/src/test/kotlin/android/support/tools/jetifier/core/transform/proguard/ClassSpecTest_MethodInitSelector.kt b/jetifier/jetifier/core/src/test/kotlin/android/support/tools/jetifier/core/transform/proguard/ClassSpecTest_MethodInitSelector.kt
new file mode 100644
index 0000000..9a792cf
--- /dev/null
+++ b/jetifier/jetifier/core/src/test/kotlin/android/support/tools/jetifier/core/transform/proguard/ClassSpecTest_MethodInitSelector.kt
@@ -0,0 +1,230 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *       http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR 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.tools.jetifier.core.transform.proguard
+
+import org.junit.Test
+
+class ClassSpecTest_MethodInitSelector {
+
+    @Test fun proGuard_methodsInitSelector() {
+        ProGuardTester
+            .forGivenPrefixes(
+            )
+            .forGivenTypesMap(
+            )
+            .testThatGivenProGuard(
+                "-keep public class * { \n" +
+                "  <methods>; \n" +
+                "}"
+            )
+            .rewritesTo(
+                "-keep public class * { \n" +
+                "  <methods>; \n" +
+                "}"
+            )
+    }
+
+    @Test fun proGuard_methodsInitSelector_modifiers() {
+        ProGuardTester
+            .forGivenPrefixes(
+            )
+            .forGivenTypesMap(
+            )
+            .testThatGivenProGuard(
+                "-keep public class * { \n" +
+                "  public <methods>; \n" +
+                "  public static <methods>; \n" +
+                "  public !static <methods>; \n" +
+                "  !private static <methods>; \n" +
+                "}"
+            )
+            .rewritesTo(
+                "-keep public class * { \n" +
+                "  public <methods>; \n" +
+                "  public static <methods>; \n" +
+                "  public !static <methods>; \n" +
+                "  !private static <methods>; \n" +
+                "}"
+            )
+    }
+
+    @Test fun proGuard_methodsInitSelector_modifiers_annotation() {
+        ProGuardTester
+            .forGivenPrefixes(
+                "support/"
+            )
+            .forGivenTypesMap(
+                "support/Annotation" to "test/Annotation"
+            )
+            .testThatGivenProGuard(
+                "-keep public class * { \n" +
+                "  @support.Annotation public <methods>; \n" +
+                "  @support.Annotation public static <methods>; \n" +
+                "  @support.Annotation public !static <methods>; \n" +
+                "  @support.Annotation !private static <methods>; \n" +
+                "}"
+            )
+            .rewritesTo(
+                "-keep public class * { \n" +
+                "  @test.Annotation public <methods>; \n" +
+                "  @test.Annotation public static <methods>; \n" +
+                "  @test.Annotation public !static <methods>; \n" +
+                "  @test.Annotation !private static <methods>; \n" +
+                "}"
+            )
+    }
+
+    @Test fun proGuard_methodInitSelector() {
+        ProGuardTester
+            .forGivenPrefixes(
+                "support/"
+            )
+            .forGivenTypesMap(
+                "support/Activity" to "test/Activity",
+                "support/Fragment" to "test/Fragment"
+            )
+            .testThatGivenProGuard(
+                "-keep public class * { \n" +
+                "  <init>(); \n" +
+                "  <init>(*); \n" +
+                "  <init>(...); \n" +
+                "  <init>(support.Activity); \n" +
+                "  <init>(support.Activity, support.Fragment, keep.Please); \n" +
+                "}"
+            )
+            .rewritesTo(
+                "-keep public class * { \n" +
+                "  <init>(); \n" +
+                "  <init>(*); \n" +
+                "  <init>(...); \n" +
+                "  <init>(test.Activity); \n" +
+                "  <init>(test.Activity, test.Fragment, keep.Please); \n" +
+                "}"
+            )
+    }
+
+    @Test fun proGuard_methodInitSelector_modifiers() {
+        ProGuardTester
+            .forGivenPrefixes(
+                "support/"
+            )
+            .forGivenTypesMap(
+                "support/Activity" to "test/Activity",
+                "support/Fragment" to "test/Fragment"
+            )
+            .testThatGivenProGuard(
+                "-keep public class * { \n" +
+                "  public <init>(); \n" +
+                "  public static <init>(*); \n" +
+                "  !public !static <init>(...); \n" +
+                "  !private static <init>(support.Activity); \n" +
+                "  public !abstract <init>(support.Activity, support.Fragment, keep.Please); \n" +
+                "}"
+            )
+            .rewritesTo(
+                "-keep public class * { \n" +
+                "  public <init>(); \n" +
+                "  public static <init>(*); \n" +
+                "  !public !static <init>(...); \n" +
+                "  !private static <init>(test.Activity); \n" +
+                "  public !abstract <init>(test.Activity, test.Fragment, keep.Please); \n" +
+                "}"
+            )
+    }
+
+    @Test fun proGuard_methodInitSelector_annotation() {
+        ProGuardTester
+            .forGivenPrefixes(
+                "support/"
+            )
+            .forGivenTypesMap(
+                "support/Activity" to "test/Activity",
+                "support/Fragment" to "test/Fragment",
+                "support/Annotation" to "test/Annotation"
+            )
+            .testThatGivenProGuard(
+                "-keep public class * { \n" +
+                "  @support.Annotation <init>(); \n" +
+                "  @support.Annotation <init>(*); \n" +
+                "  @support.Annotation <init>(...); \n" +
+                "  @keep.Me <init>(support.Activity); \n" +
+                "  @support.Annotation <init>(support.Activity, support.Fragment, keep.Please); \n" +
+                "}"
+            )
+            .rewritesTo(
+                "-keep public class * { \n" +
+                "  @test.Annotation <init>(); \n" +
+                "  @test.Annotation <init>(*); \n" +
+                "  @test.Annotation <init>(...); \n" +
+                "  @keep.Me <init>(test.Activity); \n" +
+                "  @test.Annotation <init>(test.Activity, test.Fragment, keep.Please); \n" +
+                "}"
+            )
+    }
+
+    @Test fun proGuard_methodInitSelector_modifiers_annotation() {
+        ProGuardTester
+            .forGivenPrefixes(
+                "support/"
+            )
+            .forGivenTypesMap(
+                "support/Activity" to "test/Activity",
+                "support/Fragment" to "test/Fragment",
+                "support/Annotation" to "test/Annotation"
+            )
+            .testThatGivenProGuard(
+                "-keep public class * { \n" +
+                "  @support.Annotation public <init>(); \n" +
+                "  @support.Annotation public static <init>(*); \n" +
+                "  @support.Annotation !public !static <init>(...); \n" +
+                "  @support.Annotation !private static <init>(support.Activity); \n" +
+                "  @support.Annotation public !abstract <init>(support.Activity, support.Fragment, keep.Please); \n" +
+                "}"
+            )
+            .rewritesTo(
+                "-keep public class * { \n" +
+                "  @test.Annotation public <init>(); \n" +
+                "  @test.Annotation public static <init>(*); \n" +
+                "  @test.Annotation !public !static <init>(...); \n" +
+                "  @test.Annotation !private static <init>(test.Activity); \n" +
+                "  @test.Annotation public !abstract <init>(test.Activity, test.Fragment, keep.Please); \n" +
+                "}"
+            )
+    }
+
+    @Test fun proGuard_methodInitSelector_modifiers_annotation_test() {
+        ProGuardTester
+            .forGivenPrefixes(
+                "support/"
+            )
+            .forGivenTypesMap(
+                "support/Activity" to "test/Activity",
+                "support/Fragment" to "test/Fragment",
+                "support/Annotation" to "test/Annotation"
+            )
+            .testThatGivenProGuard(
+                "-keep public class * { \n" +
+                "  @support.Annotation  public  !abstract \t <init> ( support.Activity , support.Fragment, keep.Please); \n" +
+                "}"
+            )
+            .rewritesTo(
+                "-keep public class * { \n" +
+                "  @test.Annotation  public  !abstract \t <init> (test.Activity, test.Fragment, keep.Please); \n" +
+                "}"
+            )
+    }
+}
\ No newline at end of file
diff --git a/jetifier/jetifier/core/src/test/kotlin/android/support/tools/jetifier/core/transform/proguard/ClassSpecTest_MethodSelectorWithReturnType.kt b/jetifier/jetifier/core/src/test/kotlin/android/support/tools/jetifier/core/transform/proguard/ClassSpecTest_MethodSelectorWithReturnType.kt
new file mode 100644
index 0000000..d9960b4
--- /dev/null
+++ b/jetifier/jetifier/core/src/test/kotlin/android/support/tools/jetifier/core/transform/proguard/ClassSpecTest_MethodSelectorWithReturnType.kt
@@ -0,0 +1,282 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *       http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR 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.tools.jetifier.core.transform.proguard
+
+import org.junit.Test
+
+class ClassSpecTest_MethodSelectorWithReturnType {
+
+    @Test fun proGuard_methodReturnTypeSelector() {
+        ProGuardTester
+            .forGivenPrefixes(
+                "support/"
+            )
+            .forGivenTypesMap(
+                "support/Activity" to "test/Activity",
+                "support/Fragment" to "test/Fragment"
+            )
+            .testThatGivenProGuard(
+                "-keep public class * { \n" +
+                "  void get*(); \n" +
+                "  void get*(...); \n" +
+                "  void get*(*); \n" +
+                "  void get*(support.Activity); \n" +
+                "  void get?(support.Activity); \n" +
+                "  void get(support.Activity); \n" +
+                "  void *(support.Activity, support.Fragment, keep.Please); \n" +
+                "}"
+            )
+            .rewritesTo(
+                "-keep public class * { \n" +
+                "  void get*(); \n" +
+                "  void get*(...); \n" +
+                "  void get*(*); \n" +
+                "  void get*(test.Activity); \n" +
+                "  void get?(test.Activity); \n" +
+                "  void get(test.Activity); \n" +
+                "  void *(test.Activity, test.Fragment, keep.Please); \n" +
+                "}"
+            )
+    }
+
+    @Test fun proGuard_methodReturnTypeSelector_voidResult() {
+        ProGuardTester
+            .forGivenPrefixes(
+                "support/"
+            )
+            .forGivenTypesMap(
+                "support/Activity" to "test/Activity",
+                "support/Fragment" to "test/Fragment"
+            )
+            .testThatGivenProGuard(
+                "-keep public class * { \n" +
+                "  void get(); \n" +
+                "  void get(...); \n" +
+                "  void get(*); \n" +
+                "  void get(support.Activity); \n" +
+                "  void get(support.Activity, support.Fragment, keep.Please); \n" +
+                "}"
+            )
+            .rewritesTo(
+                "-keep public class * { \n" +
+                "  void get(); \n" +
+                "  void get(...); \n" +
+                "  void get(*); \n" +
+                "  void get(test.Activity); \n" +
+                "  void get(test.Activity, test.Fragment, keep.Please); \n" +
+                "}"
+            )
+    }
+
+    @Test fun proGuard_methodReturnTypeSelector_starResult() {
+        ProGuardTester
+            .forGivenPrefixes(
+                "support/"
+            )
+            .forGivenTypesMap(
+                "support/Activity" to "test/Activity",
+                "support/Fragment" to "test/Fragment"
+            )
+            .testThatGivenProGuard(
+                "-keep public class * { \n" +
+                "  * get(); \n" +
+                "  * get(...); \n" +
+                "  * get(*); \n" +
+                "  * get(support.Activity); \n" +
+                "  * get(support.Activity, support.Fragment, keep.Please); \n" +
+                "}"
+            )
+            .rewritesTo(
+                "-keep public class * { \n" +
+                "  * get(); \n" +
+                "  * get(...); \n" +
+                "  * get(*); \n" +
+                "  * get(test.Activity); \n" +
+                "  * get(test.Activity, test.Fragment, keep.Please); \n" +
+                "}"
+            )
+    }
+
+    @Test fun proGuard_methodReturnTypeSelector_typeResult() {
+        ProGuardTester
+            .forGivenPrefixes(
+                "support/"
+            )
+            .forGivenTypesMap(
+                "support/Activity" to "test/Activity",
+                "support/Fragment" to "test/Fragment"
+            )
+            .testThatGivenProGuard(
+                "-keep public class * { \n" +
+                "  support.Fragment get(); \n" +
+                "  support.Fragment get(...); \n" +
+                "  support.Fragment get(*); \n" +
+                "  support.Fragment get(support.Activity); \n" +
+                "  support.Fragment get(support.Activity, support.Fragment, keep.Please); \n" +
+                "}"
+            )
+            .rewritesTo(
+                "-keep public class * { \n" +
+                "  test.Fragment get(); \n" +
+                "  test.Fragment get(...); \n" +
+                "  test.Fragment get(*); \n" +
+                "  test.Fragment get(test.Activity); \n" +
+                "  test.Fragment get(test.Activity, test.Fragment, keep.Please); \n" +
+                "}"
+            )
+    }
+
+    @Test fun proGuard_methodReturnTypeSelector_typeResult_wildcards() {
+        ProGuardTester
+            .forGivenPrefixes(
+                "support/"
+            )
+            .forGivenTypesMap(
+                "support/Activity" to "test/Activity",
+                "support/Fragment" to "test/Fragment"
+            )
+            .testThatGivenProGuard(
+                "-keep public class * { \n" +
+                "  support.Fragment get*(); \n" +
+                "  support.Fragment get?(...); \n" +
+                "  support.Fragment *(*); \n" +
+                "  support.Fragment *(support.Activity); \n" +
+                "  support.Fragment *(support.Activity, support.Fragment, keep.Please); \n" +
+                "}"
+            )
+            .rewritesTo(
+                "-keep public class * { \n" +
+                "  test.Fragment get*(); \n" +
+                "  test.Fragment get?(...); \n" +
+                "  test.Fragment *(*); \n" +
+                "  test.Fragment *(test.Activity); \n" +
+                "  test.Fragment *(test.Activity, test.Fragment, keep.Please); \n" +
+                "}"
+            )
+    }
+
+    @Test fun proGuard_methodReturnTypeSelector_typeResult_modifiers() {
+        ProGuardTester
+            .forGivenPrefixes(
+                "support/"
+            )
+            .forGivenTypesMap(
+                "support/Activity" to "test/Activity",
+                "support/Fragment" to "test/Fragment"
+            )
+            .testThatGivenProGuard(
+                "-keep public class * { \n" +
+                "  public support.Fragment get(); \n" +
+                "  public static support.Fragment get(...); \n" +
+                "  !public !static support.Fragment get(*); \n" +
+                "  private support.Fragment get(support.Activity); \n" +
+                "  public abstract support.Fragment get(support.Activity, support.Fragment, keep.Please); \n" +
+                "}"
+            )
+            .rewritesTo(
+                "-keep public class * { \n" +
+                "  public test.Fragment get(); \n" +
+                "  public static test.Fragment get(...); \n" +
+                "  !public !static test.Fragment get(*); \n" +
+                "  private test.Fragment get(test.Activity); \n" +
+                "  public abstract test.Fragment get(test.Activity, test.Fragment, keep.Please); \n" +
+                "}"
+            )
+    }
+
+    @Test fun proGuard_methodReturnTypeSelector_typeResult_annotation() {
+        ProGuardTester
+            .forGivenPrefixes(
+                "support/"
+            )
+            .forGivenTypesMap(
+                "support/Activity" to "test/Activity",
+                "support/Fragment" to "test/Fragment",
+                "support/Annotation" to "test/Annotation"
+            )
+            .testThatGivenProGuard(
+                "-keep public class * { \n" +
+                "  @support.Annotation support.Fragment get(); \n" +
+                "  @support.Annotation support.Fragment get(...); \n" +
+                "  @support.Annotation support.Fragment get(*); \n" +
+                "  @keep.Me support.Fragment get(support.Activity); \n" +
+                "  @support.Annotation support.Fragment get(support.Activity, support.Fragment, keep.Please); \n" +
+                "}"
+            )
+            .rewritesTo(
+                "-keep public class * { \n" +
+                "  @test.Annotation test.Fragment get(); \n" +
+                "  @test.Annotation test.Fragment get(...); \n" +
+                "  @test.Annotation test.Fragment get(*); \n" +
+                "  @keep.Me test.Fragment get(test.Activity); \n" +
+                "  @test.Annotation test.Fragment get(test.Activity, test.Fragment, keep.Please); \n" +
+                "}"
+            )
+    }
+
+    @Test fun proGuard_methodReturnTypeSelector_typeResult_modifiers_annotation() {
+        ProGuardTester
+            .forGivenPrefixes(
+                "support/"
+            )
+            .forGivenTypesMap(
+                "support/Activity" to "test/Activity",
+                "support/Fragment" to "test/Fragment",
+                "support/Annotation" to "test/Annotation"
+            )
+            .testThatGivenProGuard(
+                "-keep public class * { \n" +
+                "  @support.Annotation public support.Fragment get(); \n" +
+                "  @support.Annotation public static support.Fragment get(...); \n" +
+                "  @support.Annotation !public !static support.Fragment get(*); \n" +
+                "  @support.Annotation private support.Fragment get(support.Activity); \n" +
+                "  @support.Annotation public abstract support.Fragment get(support.Activity, support.Fragment,  keep.Please); \n" +
+                "}"
+            )
+            .rewritesTo(
+                "-keep public class * { \n" +
+                "  @test.Annotation public test.Fragment get(); \n" +
+                "  @test.Annotation public static test.Fragment get(...); \n" +
+                "  @test.Annotation !public !static test.Fragment get(*); \n" +
+                "  @test.Annotation private test.Fragment get(test.Activity); \n" +
+                "  @test.Annotation public abstract test.Fragment get(test.Activity, test.Fragment, keep.Please); \n" +
+                "}"
+            )
+    }
+
+    @Test fun proGuard_methodReturnTypeSelector_typeResult_modifiers_annotation_spaces() {
+        ProGuardTester
+            .forGivenPrefixes(
+                "support/"
+            )
+            .forGivenTypesMap(
+                "support/Activity" to "test/Activity",
+                "support/Fragment" to "test/Fragment",
+                "support/Annotation" to "test/Annotation"
+            )
+            .testThatGivenProGuard(
+                "-keep public class * { \n" +
+                "  @support.Annotation  support.Fragment \t get(support.Activity ,  support.Fragment ,  keep.Please) ; \n" +
+                "}"
+            )
+            .rewritesTo(
+                "-keep public class * { \n" +
+                "  @test.Annotation  test.Fragment \t get(test.Activity, test.Fragment, keep.Please) ; \n" +
+                "}"
+            )
+    }
+}
\ No newline at end of file
diff --git a/jetifier/jetifier/core/src/test/kotlin/android/support/tools/jetifier/core/transform/proguard/ClassSpecTest_NamedCtorSelector.kt b/jetifier/jetifier/core/src/test/kotlin/android/support/tools/jetifier/core/transform/proguard/ClassSpecTest_NamedCtorSelector.kt
new file mode 100644
index 0000000..21b8b8c
--- /dev/null
+++ b/jetifier/jetifier/core/src/test/kotlin/android/support/tools/jetifier/core/transform/proguard/ClassSpecTest_NamedCtorSelector.kt
@@ -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 android.support.tools.jetifier.core.transform.proguard
+
+import org.junit.Test
+
+class ClassSpecTest_NamedCtorSelector {
+
+    @Test fun proGuard_ctorSelector() {
+        ProGuardTester
+            .forGivenPrefixes(
+                "support/"
+            )
+            .forGivenTypesMap(
+                "support/Activity" to "test/Activity",
+                "support/Fragment" to "test/Fragment"
+            )
+            .testThatGivenProGuard(
+                "-keep public class * { \n" +
+                "  support.Activity(); \n" +
+                "  support.Activity(...); \n" +
+                "  support.Activity(*); \n" +
+                "  support.Activity(support.Activity); \n" +
+                "  support.Activity(support.Activity, support.Fragment, keep.Please); \n" +
+                "}"
+            )
+            .rewritesTo(
+                "-keep public class * { \n" +
+                "  test.Activity(); \n" +
+                "  test.Activity(...); \n" +
+                "  test.Activity(*); \n" +
+                "  test.Activity(test.Activity); \n" +
+                "  test.Activity(test.Activity, test.Fragment, keep.Please); \n" +
+                "}"
+            )
+    }
+
+    @Test fun proGuard_ctorSelector_modifiers() {
+        ProGuardTester
+            .forGivenPrefixes(
+                "support/"
+            )
+            .forGivenTypesMap(
+                "support/Activity" to "test/Activity",
+                "support/Fragment" to "test/Fragment"
+            )
+            .testThatGivenProGuard(
+                "-keep public class * { \n" +
+                "  public support.Activity(); \n" +
+                "  public static support.Activity(...); \n" +
+                "  !private support.Activity(*); \n" +
+                "  !public !static support.Activity(support.Activity); \n" +
+                "  !protected support.Activity(support.Activity, support.Fragment, keep.Please); \n" +
+                "}"
+            )
+            .rewritesTo(
+                "-keep public class * { \n" +
+                "  public test.Activity(); \n" +
+                "  public static test.Activity(...); \n" +
+                "  !private test.Activity(*); \n" +
+                "  !public !static test.Activity(test.Activity); \n" +
+                "  !protected test.Activity(test.Activity, test.Fragment, keep.Please); \n" +
+                "}"
+            )
+    }
+
+    @Test fun proGuard_ctorSelector_annotation() {
+        ProGuardTester
+            .forGivenPrefixes(
+                "support/"
+            )
+            .forGivenTypesMap(
+                "support/Activity" to "test/Activity",
+                "support/Fragment" to "test/Fragment",
+                "support/Annotation" to "test/Annotation"
+            )
+            .testThatGivenProGuard(
+                "-keep public class * { \n" +
+                "  @support.Annotation support.Activity(); \n" +
+                "  @support.Annotation support.Activity(...); \n" +
+                "  @support.Annotation support.Activity(*); \n" +
+                "  @support.Annotation support.Activity(support.Activity); \n" +
+                "  @support.Annotation support.Activity(support.Activity, support.Fragment, keep.Please); \n" +
+                "}"
+            )
+            .rewritesTo(
+                "-keep public class * { \n" +
+                "  @test.Annotation test.Activity(); \n" +
+                "  @test.Annotation test.Activity(...); \n" +
+                "  @test.Annotation test.Activity(*); \n" +
+                "  @test.Annotation test.Activity(test.Activity); \n" +
+                "  @test.Annotation test.Activity(test.Activity, test.Fragment, keep.Please); \n" +
+                "}"
+            )
+    }
+
+    @Test fun proGuard_ctorSelector_modifiers_annotation() {
+        ProGuardTester
+            .forGivenPrefixes(
+                "support/"
+            )
+            .forGivenTypesMap(
+                "support/Activity" to "test/Activity",
+                "support/Fragment" to "test/Fragment",
+                "support/Annotation" to "test/Annotation"
+            )
+            .testThatGivenProGuard(
+                "-keep public class * { \n" +
+                "  @support.Annotation public support.Activity(); \n" +
+                "  @support.Annotation public static support.Activity(...); \n" +
+                "  @support.Annotation !private support.Activity(*); \n" +
+                "  @support.Annotation !public !static support.Activity(support.Activity); \n" +
+                "  @support.Annotation !protected support.Activity(support.Activity, support.Fragment, keep.Please); \n" +
+                "}"
+            )
+            .rewritesTo(
+                "-keep public class * { \n" +
+                "  @test.Annotation public test.Activity(); \n" +
+                "  @test.Annotation public static test.Activity(...); \n" +
+                "  @test.Annotation !private test.Activity(*); \n" +
+                "  @test.Annotation !public !static test.Activity(test.Activity); \n" +
+                "  @test.Annotation !protected test.Activity(test.Activity, test.Fragment, keep.Please); \n" +
+                "}"
+            )
+    }
+
+    @Test fun proGuard_ctorSelector_modifiers_annotation_spaces() {
+        ProGuardTester
+            .forGivenPrefixes(
+                "support/"
+            )
+            .forGivenTypesMap(
+                "support/Activity" to "test/Activity",
+                "support/Fragment" to "test/Fragment",
+                "support/Annotation" to "test/Annotation"
+            )
+            .testThatGivenProGuard(
+                "-keep public class * { \n" +
+                "  @support.Annotation  !protected \t support.Activity( support.Activity ); \n" +
+                "}"
+            )
+            .rewritesTo(
+                "-keep public class * { \n" +
+                "  @test.Annotation  !protected \t test.Activity(test.Activity); \n" +
+                "}"
+            )
+    }
+}
\ No newline at end of file
diff --git a/jetifier/jetifier/core/src/test/kotlin/android/support/tools/jetifier/core/transform/proguard/ProGuardTester.kt b/jetifier/jetifier/core/src/test/kotlin/android/support/tools/jetifier/core/transform/proguard/ProGuardTester.kt
new file mode 100644
index 0000000..cae21d0
--- /dev/null
+++ b/jetifier/jetifier/core/src/test/kotlin/android/support/tools/jetifier/core/transform/proguard/ProGuardTester.kt
@@ -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.support.tools.jetifier.core.transform.proguard
+
+import android.support.tools.jetifier.core.archive.ArchiveFile
+import android.support.tools.jetifier.core.config.Config
+import android.support.tools.jetifier.core.map.TypesMap
+import android.support.tools.jetifier.core.rules.JavaType
+import android.support.tools.jetifier.core.transform.TransformationContext
+import com.google.common.truth.Truth
+import java.nio.charset.StandardCharsets
+import java.nio.file.Paths
+
+
+/**
+ * Helper to test ProGuard rewriting logic using lightweight syntax.
+ */
+object ProGuardTester {
+
+    private var javaTypes = emptyList<Pair<String, String>>()
+    private var proGuardTypes = emptyList<Pair<ProGuardType, ProGuardType>>()
+    private var prefixes = emptyList<String>()
+
+    fun forGivenPrefixes(vararg prefixes: String) : ProGuardTester {
+        this.prefixes = prefixes.toList()
+        return this
+    }
+
+    fun forGivenTypesMap(vararg rules: Pair<String, String>) : ProGuardTester {
+        this.javaTypes = rules.toList()
+        return this
+    }
+
+    fun forGivenProGuardMap(vararg rules: Pair<String, String>) : ProGuardTester {
+        this.proGuardTypes = rules.map {
+            ProGuardType.fromDotNotation(it.first) to ProGuardType.fromDotNotation(it.second) }
+            .toList()
+        return this
+    }
+
+    fun testThatGivenType(givenType: String) : ProGuardTesterForType {
+        return ProGuardTesterForType(createConfig(), givenType)
+    }
+
+    fun testThatGivenArguments(givenArgs: String) : ProGuardTesterForArgs {
+        return ProGuardTesterForArgs(createConfig(), givenArgs)
+    }
+
+    fun testThatGivenProGuard(given: String) : ProGuardTesterForFile {
+        return ProGuardTesterForFile(createConfig(), given)
+    }
+
+    private fun createConfig() : Config {
+        return Config(
+            restrictToPackagePrefixes = prefixes,
+            rewriteRules = emptyList(),
+            pomRewriteRules =  emptyList(),
+            typesMap = TypesMap(
+                types = javaTypes.map { JavaType(it.first) to JavaType(it.second) }.toMap(),
+                fields = emptyMap()),
+            proGuardMap = ProGuardTypesMap(proGuardTypes.toMap()))
+    }
+
+
+    class ProGuardTesterForFile(private val config: Config, private val given: String) {
+
+        fun rewritesTo(expected: String) {
+            val context = TransformationContext(config)
+            val transformer = ProGuardTransformer(context)
+            val file = ArchiveFile(Paths.get("proguard.txt"), given.toByteArray())
+            transformer.runTransform(file)
+
+            val result = file.data.toString(StandardCharsets.UTF_8)
+
+            Truth.assertThat(result).isEqualTo(expected)
+        }
+
+    }
+
+    class ProGuardTesterForType(private val config: Config, private val given: String) {
+
+        fun getsRewrittenTo(expectedType: String) {
+            val context = TransformationContext(config)
+            val mapper = ProGuardTypesMapper(context)
+            val result = mapper.replaceType(given)
+
+            Truth.assertThat(result).isEqualTo(expectedType)
+        }
+
+    }
+
+    class ProGuardTesterForArgs(private val config: Config, private val given: String) {
+
+        fun getRewrittenTo(expectedArguments: String) {
+            val context = TransformationContext(config)
+            val mapper = ProGuardTypesMapper(context)
+            val result = mapper.replaceMethodArgs(given)
+
+            Truth.assertThat(result).isEqualTo(expectedArguments)
+        }
+    }
+
+}
+
diff --git a/jetifier/jetifier/core/src/test/kotlin/android/support/tools/jetifier/core/transform/proguard/ProGuardTypesMapperTest.kt b/jetifier/jetifier/core/src/test/kotlin/android/support/tools/jetifier/core/transform/proguard/ProGuardTypesMapperTest.kt
new file mode 100644
index 0000000..5e12aff
--- /dev/null
+++ b/jetifier/jetifier/core/src/test/kotlin/android/support/tools/jetifier/core/transform/proguard/ProGuardTypesMapperTest.kt
@@ -0,0 +1,185 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *       http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR 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.tools.jetifier.core.transform.proguard
+
+import org.junit.Test
+
+class ProGuardTypesMapperTest {
+
+    @Test fun proGuard_typeMapper_wildcard_simple() {
+        ProGuardTester
+            .testThatGivenType("*")
+            .getsRewrittenTo("*")
+    }
+
+    @Test fun proGuard_typeMapper_wildcard_double() {
+        ProGuardTester
+            .testThatGivenType("**")
+            .getsRewrittenTo("**")
+    }
+
+    @Test fun proGuard_typeMapper_wildcard_composed() {
+        ProGuardTester
+            .testThatGivenType("**/*")
+            .getsRewrittenTo("**/*")
+    }
+
+    @Test fun proGuard_typeMapper_wildcard_viaMap() {
+        ProGuardTester
+            .forGivenPrefixes(
+                "support/"
+            )
+            .forGivenProGuardMap(
+                "support/v7/*" to "test/v7/*"
+            )
+            .testThatGivenType("support.v7.*")
+            .getsRewrittenTo("test.v7.*")
+    }
+
+    @Test fun proGuard_typeMapper_wildcard_viaMap2() {
+        ProGuardTester
+            .forGivenPrefixes(
+                "support/"
+            )
+            .forGivenProGuardMap(
+                "support/v7/**" to "test/v7/**"
+            )
+            .testThatGivenType("support.v7.**")
+            .getsRewrittenTo("test.v7.**")
+    }
+
+    @Test fun proGuard_typeMapper_wildcard_viaTypesMap() {
+        ProGuardTester
+            .forGivenPrefixes(
+                "support/"
+            )
+            .forGivenTypesMap(
+                "support/v7/Activity" to "test/v7/Activity"
+            )
+            .testThatGivenType("support.v7.Activity")
+            .getsRewrittenTo("test.v7.Activity")
+    }
+
+    @Test fun proGuard_typeMapper_wildcard_notFoundInMap() {
+        ProGuardTester
+            .forGivenPrefixes(
+                "support/"
+            )
+            .forGivenProGuardMap(
+                "support/**" to "test/**"
+            )
+            .testThatGivenType("keep.me.**")
+            .getsRewrittenTo("keep.me.**")
+    }
+
+    @Test fun proGuard_typeMapper_differentPrefix_notRewritten() {
+        ProGuardTester
+            .forGivenPrefixes(
+                "support/"
+            )
+            .forGivenTypesMap(
+                "hello/Activity" to "test/Activity"
+            )
+            .testThatGivenType("hello.Activity")
+            .getsRewrittenTo("hello.Activity")
+    }
+
+    @Test fun proGuard_typeMapper_differentPrefix_wildcard_getsRewritten() {
+        ProGuardTester
+            .forGivenPrefixes(
+                "support/"
+            )
+            .forGivenProGuardMap(
+                "hello/**" to "test/**"
+            )
+            .testThatGivenType("hello.**")
+            .getsRewrittenTo("test.**")
+    }
+
+    @Test fun proGuard_typeMapper_innerClass() {
+        ProGuardTester
+            .forGivenPrefixes(
+                "support/"
+            )
+            .forGivenTypesMap(
+                "support/Activity\$InnerClass" to "test/Activity\$InnerClass"
+            )
+            .testThatGivenType("support.Activity\$InnerClass")
+            .getsRewrittenTo("test.Activity\$InnerClass")
+    }
+
+    @Test fun proGuard_typeMapper_innerClass_wildcard() {
+        ProGuardTester
+            .forGivenPrefixes(
+                "support/"
+            )
+            .forGivenProGuardMap(
+                "**R\$Attrs" to "**R2\$Attrs"
+            )
+            .testThatGivenType("**R\$Attrs")
+            .getsRewrittenTo("**R2\$Attrs")
+    }
+
+    @Test fun proGuard_argsMapper_tripleDots() {
+        ProGuardTester
+            .testThatGivenArguments("...")
+            .getRewrittenTo("...")
+    }
+
+    @Test fun proGuard_argsMapper_wildcard() {
+        ProGuardTester
+            .testThatGivenArguments("*")
+            .getRewrittenTo("*")
+    }
+
+    @Test fun proGuard_argsMapper_wildcards() {
+        ProGuardTester
+            .testThatGivenArguments("**, **")
+            .getRewrittenTo("**, **")
+    }
+
+    @Test fun proGuard_argsMapper_viaMaps() {
+        ProGuardTester
+            .forGivenPrefixes(
+                "support/"
+            )
+            .forGivenTypesMap(
+                "support/Activity" to "test/Activity"
+            )
+            .forGivenProGuardMap(
+                "support/v7/**" to "test/v7/**"
+            )
+            .testThatGivenArguments("support.Activity, support.v7.**, keep.Me")
+            .getRewrittenTo("test.Activity, test.v7.**, keep.Me")
+    }
+
+    @Test fun proGuard_argsMapper_viaMaps_spaces() {
+        ProGuardTester
+            .forGivenPrefixes(
+                "support/"
+            )
+            .forGivenTypesMap(
+                "support/Activity" to "test/Activity"
+            )
+            .forGivenProGuardMap(
+                "support/v7/**" to "test/v7/**"
+            )
+            .testThatGivenArguments(" support.Activity , \t support.v7.**,  keep.Me ")
+            .getRewrittenTo("test.Activity, test.v7.**, keep.Me")
+    }
+
+}
\ No newline at end of file
diff --git a/jetifier/jetifier/core/src/test/kotlin/android/support/tools/jetifier/core/transform/proguard/ProguardSamplesTest.kt b/jetifier/jetifier/core/src/test/kotlin/android/support/tools/jetifier/core/transform/proguard/ProguardSamplesTest.kt
new file mode 100644
index 0000000..0542e7d
--- /dev/null
+++ b/jetifier/jetifier/core/src/test/kotlin/android/support/tools/jetifier/core/transform/proguard/ProguardSamplesTest.kt
@@ -0,0 +1,274 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *       http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR 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.tools.jetifier.core.transform.proguard
+
+import org.junit.Test
+
+class ProguardSamplesTest {
+
+    @Test fun proGuard_sample() {
+        ProGuardTester
+            .forGivenPrefixes(
+                "android/app/",
+                "android/view/",
+                "android/content/",
+                "android/os/",
+                "android/webkit/"
+            )
+            .forGivenTypesMap(
+                "android/app/Activity" to "test/app/Activity",
+                "android/app/Application" to "test/app/Application",
+                "android/view/View" to "test/view/View",
+                "android/view/MenuItem" to "test/view/MenuItem",
+                "android/content/Context" to "test/content/Context",
+                "android/os/Parcelable" to "test/os/Parcelable",
+                "android/webkit/JavascriptInterface" to "test/webkit/JavascriptInterface"
+            )
+            .testThatGivenProGuard(
+               "-injars      bin/classes \n" +
+               "-injars      libs \n" +
+               "-outjars     bin/classes-processed.jar \n" +
+               "-libraryjars /usr/local/java/android-sdk/platforms/android-9/android.jar \n" +
+               "\n" +
+               "-dontpreverify \n" +
+               "-repackageclasses '' \n" +
+               "-allowaccessmodification \n" +
+               "-optimizations !code/simplification/arithmetic \n" +
+               "-keepattributes *Annotation* \n" +
+               "\n" +
+               "-keep public class * extends android.app.Activity \n" +
+               "-keep public class * extends android.app.Application \n" +
+               " \n" +
+               "-keep public class * extends android.view.View { \n" +
+               "      public <init>(android.content.Context); \n" +
+               "      public <init>(android.content.Context, android.util.AttributeSet); \n" +
+               "      public <init>(android.content.Context, android.util.AttributeSet, int); \n" +
+               "      public void set*(...); \n" +
+               "} \n" +
+               "\n" +
+               "-keepclasseswithmembers class * { \n" +
+               "    public <init>(android.content.Context, android.util.AttributeSet); \n" +
+               "} \n" +
+               "\n" +
+               "-keepclasseswithmembers class * { \n" +
+               "    public <init>(android.content.Context, android.util.AttributeSet, int); \n" +
+               "} \n" +
+               "\n" +
+               "-keepclassmembers class * extends android.content.Context { \n" +
+               "    public void *(android.view.View); \n" +
+               "    public void *(android.view.MenuItem); \n" +
+               "} \n" +
+               "\n" +
+               "-keepclassmembers class * implements android.os.Parcelable { \n" +
+               "    static ** CREATOR; \n" +
+               "} \n" +
+               "\n" +
+               "-keepclassmembers class **.R\$* { \n" +
+               "    public static <fields>; \n" +
+               "} \n" +
+               "\n" +
+               "-keepclassmembers class * { \n" +
+               "    @android.webkit.JavascriptInterface <methods>; \n" +
+               "} "
+            )
+            .rewritesTo(
+                "-injars      bin/classes \n" +
+                "-injars      libs \n" +
+                "-outjars     bin/classes-processed.jar \n" +
+                "-libraryjars /usr/local/java/android-sdk/platforms/android-9/android.jar \n" +
+                "\n" +
+                "-dontpreverify \n" +
+                "-repackageclasses '' \n" +
+                "-allowaccessmodification \n" +
+                "-optimizations !code/simplification/arithmetic \n" +
+                "-keepattributes *Annotation* \n" +
+                "\n" +
+                "-keep public class * extends test.app.Activity \n" +
+                "-keep public class * extends test.app.Application \n" +
+                " \n" +
+                "-keep public class * extends test.view.View { \n" +
+                "      public <init>(test.content.Context); \n" +
+                "      public <init>(test.content.Context, android.util.AttributeSet); \n" +
+                "      public <init>(test.content.Context, android.util.AttributeSet, int); \n" +
+                "      public void set*(...); \n" +
+                "} \n" +
+                "\n" +
+                "-keepclasseswithmembers class * { \n" +
+                "    public <init>(test.content.Context, android.util.AttributeSet); \n" +
+                "} \n" +
+                "\n" +
+                "-keepclasseswithmembers class * { \n" +
+                "    public <init>(test.content.Context, android.util.AttributeSet, int); \n" +
+                "} \n" +
+                "\n" +
+                "-keepclassmembers class * extends test.content.Context { \n" +
+                "    public void *(test.view.View); \n" +
+                "    public void *(test.view.MenuItem); \n" +
+                "} \n" +
+                "\n" +
+                "-keepclassmembers class * implements test.os.Parcelable { \n" +
+                "    static ** CREATOR; \n" +
+                "} \n" +
+                "\n" +
+                "-keepclassmembers class **.R\$* { \n" +
+                "    public static <fields>; \n" +
+                "} \n" +
+                "\n" +
+                "-keepclassmembers class * { \n" +
+                "    @test.webkit.JavascriptInterface <methods>; \n" +
+                "} "
+            )
+    }
+
+    @Test fun proGuard_sample2() {
+        ProGuardTester
+            .forGivenPrefixes(
+                "android/support/v7/"
+            )
+            .forGivenTypesMap(
+                "android/support/v7/preference/Preference" to "test/Preference"
+            )
+            .testThatGivenProGuard(
+                "-keep public class android.support.v7.preference.Preference {\n" +
+                "  public <init>(android.content.Context, android.util.AttributeSet);\n" +
+                "}\n" +
+                "-keep public class * extends android.support.v7.preference.Preference {\n" +
+                "  public <init>(android.content.Context, android.util.AttributeSet);\n" +
+                "}"
+            )
+            .rewritesTo(
+                "-keep public class test.Preference {\n" +
+                "  public <init>(android.content.Context, android.util.AttributeSet);\n" +
+                "}\n" +
+                "-keep public class * extends test.Preference {\n" +
+                "  public <init>(android.content.Context, android.util.AttributeSet);\n" +
+                "}"
+            )
+    }
+
+    @Test fun proGuard_sample3() {
+        ProGuardTester
+            .forGivenPrefixes(
+                "android/support/design/",
+                "android/support/v7/"
+            )
+            .forGivenTypesMap(
+                "support/Fragment" to "test/Fragment",
+                "android/support/v7/widget/RoundRectDrawable" to "test/RoundRectDrawable"
+            )
+            .forGivenProGuardMap(
+                "android/support/design.**" to "test/design.**",
+                "android/support/design/R\$*" to "test/design/R\$*"
+            )
+            .testThatGivenProGuard(
+                "-dontwarn android.support.design.**\n" +
+                "-keep class android.support.design.** { *; }\n" +
+                "-keep interface android.support.design.** { *; }\n" +
+                "-keep public class android.support.design.R\$* { *; }\n" +
+                "-keep class android.support.v7.widget.RoundRectDrawable { *; }"
+            )
+            .rewritesTo(
+                "-dontwarn test.design.**\n" +
+                "-keep class test.design.** { *; }\n" +
+                "-keep interface test.design.** { *; }\n" +
+                "-keep public class test.design.R\$* { *; }\n" +
+                "-keep class test.RoundRectDrawable { *; }"
+            )
+    }
+
+    @Test fun proGuard_sample4() {
+        ProGuardTester
+            .forGivenPrefixes(
+                "android/support/design/",
+                "android/support/v7/",
+                "android/support/v4/"
+            )
+            .forGivenTypesMap(
+                "android/support/v7/widget/LinearLayoutManager" to "test/LinearLayoutManager",
+                "android/support/v4/view/ActionProvider" to "test/ActionProvider"
+            )
+            .forGivenProGuardMap(
+                "android/support/v7/**" to "test/v7/**",
+                "android/support/v7/widget/**" to "test/v7/widget/**",
+                "android/support/v7/internal/widget/**" to "test/v7/internal/widget/**",
+                "android/support/v7/internal/**" to "test/v7/internal/**"
+            )
+            .testThatGivenProGuard(
+                "-dontwarn android.support.v7.**\n" +
+                "-keep public class android.support.v7.widget.** { *; }\n" +
+                "-keep public class android.support.v7.internal.widget.** { *; }\n" +
+                "-keep class android.support.v7.widget.LinearLayoutManager { *; }\n" +
+                "-keep class android.support.v7.internal.** { *; }\n" +
+                "-keep interface android.support.v7.internal.** { *; }\n" +
+                "\n" +
+                "-keep class android.support.v7.** { *; }\n" +
+                "-keep interface android.support.v7.** { *; }\n" +
+                "\n" +
+                "-keep public class * extends android.support.v4.view.ActionProvider {\n" +
+                "    public <init>(android.content.Context);"
+            )
+            .rewritesTo(
+                "-dontwarn test.v7.**\n" +
+                "-keep public class test.v7.widget.** { *; }\n" +
+                "-keep public class test.v7.internal.widget.** { *; }\n" +
+                "-keep class test.LinearLayoutManager { *; }\n" +
+                "-keep class test.v7.internal.** { *; }\n" +
+                "-keep interface test.v7.internal.** { *; }\n" +
+                "\n" +
+                "-keep class test.v7.** { *; }\n" +
+                "-keep interface test.v7.** { *; }\n" +
+                "\n" +
+                "-keep public class * extends test.ActionProvider {\n" +
+                "    public <init>(android.content.Context);"
+            )
+    }
+
+    @Test fun proGuard_sample5() {
+        ProGuardTester
+            .forGivenPrefixes(
+                "support/"
+            )
+            .forGivenTypesMap(
+                "support/Activity" to "test/Activity",
+                "support/Fragment" to "test/Fragment",
+                "support/Annotation" to "test/Annotation"
+            )
+            .testThatGivenProGuard(
+                "-keep public class * extends support.Activity { \n" +
+                "  public static <fields>; \n" +
+                "  public !static <methods>; \n" +
+                "  public support.Fragment height; \n" +
+                "  public static <fields>; \n" +
+                "  public not.related.Type width; public support.Fragment width; \n" +
+                "  ignoreMe; \n" +
+                "  @support.Annotation public support.Fragment get(); \n" +
+                "}"
+            )
+            .rewritesTo(
+                "-keep public class * extends test.Activity { \n" +
+                "  public static <fields>; \n" +
+                "  public !static <methods>; \n" +
+                "  public test.Fragment height; \n" +
+                "  public static <fields>; \n" +
+                "  public not.related.Type width; public test.Fragment width; \n" +
+                "  ignoreMe; \n" +
+                "  @test.Annotation public test.Fragment get(); \n" +
+                "}"
+            )
+    }
+
+}
\ No newline at end of file
diff --git a/jetifier/jetifier/core/src/test/kotlin/android/support/tools/jetifier/core/transform/resource/XmlResourcesTransformerTest.kt b/jetifier/jetifier/core/src/test/kotlin/android/support/tools/jetifier/core/transform/resource/XmlResourcesTransformerTest.kt
new file mode 100644
index 0000000..5788b40
--- /dev/null
+++ b/jetifier/jetifier/core/src/test/kotlin/android/support/tools/jetifier/core/transform/resource/XmlResourcesTransformerTest.kt
@@ -0,0 +1,255 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *       http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR 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.tools.jetifier.core.transform.resource
+
+import android.support.tools.jetifier.core.config.Config
+import android.support.tools.jetifier.core.rules.JavaType
+import android.support.tools.jetifier.core.map.TypesMap
+import android.support.tools.jetifier.core.transform.TransformationContext
+import android.support.tools.jetifier.core.transform.proguard.ProGuardTypesMap
+import com.google.common.truth.Truth
+import org.junit.Test
+import java.nio.charset.Charset
+
+class XmlResourcesTransformerTest {
+
+    @Test fun layout_noPrefix_noChange() {
+        testRewriteToTheSame(
+            givenAndExpectedXml =
+                "<android.support.v7.preference.Preference>\n" +
+                "</android.support.v7.preference.Preference>",
+            prefixes = listOf(),
+            map = mapOf(
+                "android/support/v7/preference/Preference" to "android/test/pref/Preference"
+            )
+        )
+    }
+
+    @Test fun layout_noRule_noChange() {
+        testRewriteToTheSame(
+            givenAndExpectedXml =
+                "<android.support.v7.preference.Preference>\n" +
+                "</android.support.v7.preference.Preference>",
+            prefixes = listOf("android/support/v7/"),
+            map = mapOf()
+        )
+    }
+
+    @Test fun layout_notApplicablePrefix_noChange() {
+        testRewriteToTheSame(
+            givenAndExpectedXml =
+                "<android.support.v7.preference.Preference>\n" +
+                "</android.support.v7.preference.Preference>",
+            prefixes = listOf("android/support/v14/"),
+            map = mapOf(
+                "android/support/v7/preference/Preference" to "android/test/pref/Preference"
+            )
+        )
+    }
+
+    @Test fun layout_notApplicablePrefix2_noChange() {
+        testRewriteToTheSame(
+            givenAndExpectedXml =
+                "<my.android.support.v7.preference.Preference>\n" +
+                "</my.android.support.v7.preference.Preference>",
+            prefixes = listOf("android/support/v7/"),
+            map = mapOf(
+                "android/support/v7/preference/Preference" to "android/test/pref/Preference"
+            )
+        )
+    }
+
+    @Test fun layout_notApplicableRule_noChange() {
+        testRewriteToTheSame(
+            givenAndExpectedXml =
+                "<android.support.v7.preference.Preference>\n" +
+                "</android.support.v7.preference.Preference>",
+            prefixes = listOf("android/support/"),
+            map = mapOf(
+                "android/support2/v7/preference/Preference" to "android/test/pref/Preference"
+            )
+        )
+    }
+
+    @Test fun layout_onePrefix_oneRule_oneRewrite() {
+        testRewrite(
+            givenXml =
+                "<android.support.v7.preference.Preference/>",
+            expectedXml =
+                "<android.test.pref.Preference/>",
+            prefixes = listOf("android/support/"),
+            map = mapOf(
+                "android/support/v7/preference/Preference" to "android/test/pref/Preference"
+            )
+        )
+    }
+
+    @Test fun layout_onePrefix_oneRule_attribute_oneRewrite() {
+        testRewrite(
+            givenXml =
+                "<android.support.v7.preference.Preference \n" +
+                "    someAttribute=\"android.support.v7.preference.Preference\"/>",
+            expectedXml =
+                "<android.test.pref.Preference \n" +
+                "    someAttribute=\"android.support.v7.preference.Preference\"/>",
+            prefixes = listOf("android/support/"),
+            map =  mapOf(
+                "android/support/v7/preference/Preference" to "android/test/pref/Preference"
+            )
+        )
+    }
+
+    @Test fun layout_onePrefix_oneRule_twoRewrites() {
+        testRewrite(
+            givenXml =
+                "<android.support.v7.preference.Preference>\n" +
+                "</android.support.v7.preference.Preference>",
+            expectedXml =
+                "<android.test.pref.Preference>\n" +
+                "</android.test.pref.Preference>",
+            prefixes = listOf("android/support/"),
+            map = mapOf(
+                "android/support/v7/preference/Preference" to "android/test/pref/Preference"
+            )
+        )
+    }
+
+    @Test fun layout_onePrefix_oneRule_viewTag_simple() {
+        testRewrite(
+            givenXml =
+                "<view class=\"android.support.v7.preference.Preference\">",
+            expectedXml =
+                "<view class=\"android.test.pref.Preference\">",
+            prefixes = listOf("android/support/"),
+            map = mapOf(
+                "android/support/v7/preference/Preference" to "android/test/pref/Preference"
+            )
+        )
+    }
+
+    @Test fun layout_onePrefix_oneRule_viewTag_stuffAround() {
+        testRewrite(
+            givenXml =
+                "<view notRelated=\"true\" " +
+                "      class=\"android.support.v7.preference.Preference\"" +
+                "      ignoreMe=\"android.support.v7.preference.Preference\">",
+            expectedXml =
+                "<view notRelated=\"true\" " +
+                "      class=\"android.test.pref.Preference\"" +
+                "      ignoreMe=\"android.support.v7.preference.Preference\">",
+            prefixes = listOf("android/support/"),
+            map = mapOf(
+                "android/support/v7/preference/Preference" to "android/test/pref/Preference"
+            )
+        )
+    }
+
+    @Test fun layout_onePrefix_oneRule_viewInText_notMatched() {
+        testRewriteToTheSame(
+            givenAndExpectedXml =
+                "<test attribute=\"view\" class=\"android.support.v7.preference.Preference\">",
+            prefixes = listOf("android/support/"),
+            map = mapOf(
+                "android/support/v7/preference/Preference" to "android/test/pref/Preference"
+            )
+        )
+    }
+
+    @Test fun layout_onePrefix_oneRule_identity() {
+        testRewrite(
+            givenXml =
+                "<android.support.v7.preference.Preference>\n" +
+                "</android.support.v7.preference.Preference>",
+            expectedXml =
+                "<android.support.v7.preference.Preference>\n" +
+                "</android.support.v7.preference.Preference>",
+            prefixes = listOf("android/support/"),
+            map = mapOf(
+                "android/support/v7/preference/Preference" to "android/support/v7/preference/Preference"
+            )
+        )
+    }
+
+    @Test fun layout_twoPrefixes_threeRules_multipleRewrites() {
+        testRewrite(
+            givenXml =
+                "<android.support.v7.preference.Preference>\n" +
+                "  <android.support.v14.preference.DialogPreference" +
+                "      someAttribute=\"someValue\"/>\n" +
+                "  <android.support.v14.preference.DialogPreference" +
+                "      someAttribute=\"someValue2\"/>\n" +
+                "  <!-- This one should be ignored --> \n" +
+                "  <android.support.v21.preference.DialogPreference" +
+                "      someAttribute=\"someValue2\"/>\n" +
+                "</android.support.v7.preference.Preference>\n" +
+                "\n" +
+                "<android.support.v7.preference.ListPreference/>",
+            expectedXml =
+                "<android.test.pref.Preference>\n" +
+                "  <android.test14.pref.DialogPreference" +
+                "      someAttribute=\"someValue\"/>\n" +
+                "  <android.test14.pref.DialogPreference" +
+                "      someAttribute=\"someValue2\"/>\n" +
+                "  <!-- This one should be ignored --> \n" +
+                "  <android.support.v21.preference.DialogPreference" +
+                "      someAttribute=\"someValue2\"/>\n" +
+                "</android.test.pref.Preference>\n" +
+                "\n" +
+                "<android.test.pref.ListPref/>",
+            prefixes = listOf(
+                "android/support/v7/",
+                "android/support/v14/"
+            ),
+            map = mapOf(
+                "android/support/v7/preference/ListPreference" to "android/test/pref/ListPref",
+                "android/support/v7/preference/Preference" to "android/test/pref/Preference",
+                "android/support/v14/preference/DialogPreference" to "android/test14/pref/DialogPreference",
+                "android/support/v21/preference/DialogPreference" to "android/test21/pref/DialogPreference"
+            )
+        )
+    }
+
+    private fun testRewriteToTheSame(givenAndExpectedXml: String,
+                                     prefixes: List<String>,
+                                     map: Map<String, String>) {
+        testRewrite(givenAndExpectedXml, givenAndExpectedXml, prefixes, map)
+    }
+
+    private fun testRewrite(givenXml : String,
+                            expectedXml : String,
+                            prefixes: List<String>,
+                            map: Map<String, String>) {
+        val given =
+            "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n" +
+            "$givenXml\n"
+
+        val expected =
+            "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n" +
+            "$expectedXml\n"
+
+        val typesMap = TypesMap(map.map{ JavaType(it.key) to JavaType(it.value) }.toMap(),
+            emptyMap())
+        val config = Config(prefixes, emptyList(), emptyList(), typesMap, ProGuardTypesMap.EMPTY)
+        val context = TransformationContext(config)
+        val processor = XmlResourcesTransformer(context)
+        val result = processor.transform(given.toByteArray())
+        val strResult = result.toString(Charset.defaultCharset())
+
+        Truth.assertThat(strResult).isEqualTo(expected)
+    }
+}
+
diff --git a/jetifier/jetifier/gradle-plugin/build.gradle b/jetifier/jetifier/gradle-plugin/build.gradle
new file mode 100644
index 0000000..710c69f
--- /dev/null
+++ b/jetifier/jetifier/gradle-plugin/build.gradle
@@ -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
+ */
+
+version '1.0'
+
+apply plugin: 'maven-publish'
+
+dependencies {
+    compile project(':core')
+    compileOnly gradleApi()
+}
+
+// Task to create a jar with all the required dependencies bundled inside
+task fatJar(type: Jar) {
+    baseName = project.name + '-all'
+    from { configurations.runtime.collect { it.isDirectory() ? it : zipTree(it) } }
+    with jar
+}
+
+publishing {
+    publications {
+        mavenJava(MavenPublication) {
+            from components.java
+        }
+    }
+}
\ No newline at end of file
diff --git a/jetifier/jetifier/gradle-plugin/src/main/kotlin/android/support/tools/jetifier/plugin/gradle/JetifierExtension.kt b/jetifier/jetifier/gradle-plugin/src/main/kotlin/android/support/tools/jetifier/plugin/gradle/JetifierExtension.kt
new file mode 100644
index 0000000..e8acb25
--- /dev/null
+++ b/jetifier/jetifier/gradle-plugin/src/main/kotlin/android/support/tools/jetifier/plugin/gradle/JetifierExtension.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.support.tools.jetifier.plugin.gradle
+
+import org.gradle.api.Project
+import org.gradle.api.artifacts.Dependency
+import org.gradle.api.file.FileCollection
+
+
+/**
+ * Defines methods that can be used in gradle on the "jetifier" object and triggers [JetifierTask].
+ */
+open class JetifierExtension(val project : Project) {
+
+    companion object {
+        const val TASK_NAME : String = "Jetifier"
+    }
+
+    /**
+     * Handles dependency defined via string notation (like group:artifact:version).
+     */
+    fun process(dependencyNotation: String): FileCollection {
+        return process(project.getDependencies().create(dependencyNotation))
+    }
+
+    /**
+     * Handles dependency.
+     */
+    fun process(dependency: Dependency): FileCollection {
+        val configuration = project.configurations.detachedConfiguration()
+        configuration.dependencies.add(dependency)
+        return process(configuration)
+    }
+
+    /**
+     * Handles collections of files. Defined e.g. via files() or directly from a configuration.
+     */
+    fun process(files: FileCollection): FileCollection {
+        var task = project.tasks.findByName(TASK_NAME) as JetifierTask?
+        if (task == null) {
+            task = project.tasks.create(TASK_NAME, JetifierTask::class.java)
+        }
+        task!!.addFilesToProcess(files)
+        return task.outputs.files
+    }
+
+}
\ No newline at end of file
diff --git a/jetifier/jetifier/gradle-plugin/src/main/kotlin/android/support/tools/jetifier/plugin/gradle/JetifierLoggerAdapter.kt b/jetifier/jetifier/gradle-plugin/src/main/kotlin/android/support/tools/jetifier/plugin/gradle/JetifierLoggerAdapter.kt
new file mode 100644
index 0000000..9d85851
--- /dev/null
+++ b/jetifier/jetifier/gradle-plugin/src/main/kotlin/android/support/tools/jetifier/plugin/gradle/JetifierLoggerAdapter.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.support.tools.jetifier.plugin.gradle
+
+import android.support.tools.jetifier.core.utils.LogConsumer
+import org.gradle.api.logging.Logger
+
+/**
+ * Logging adapter to hook jetfier logging into gradle.
+ */
+class JetifierLoggerAdapter(val gradleLogger: Logger) : LogConsumer {
+
+    override fun error(message: String) {
+        gradleLogger.error(message)
+    }
+
+    override fun info(message: String) {
+        gradleLogger.info(message)
+    }
+
+    override fun verbose(message: String) {
+        gradleLogger.info(message)
+    }
+
+    override fun debug(message: String) {
+        gradleLogger.debug(message)
+    }
+
+}
\ No newline at end of file
diff --git a/jetifier/jetifier/gradle-plugin/src/main/kotlin/android/support/tools/jetifier/plugin/gradle/JetifierPlugin.kt b/jetifier/jetifier/gradle-plugin/src/main/kotlin/android/support/tools/jetifier/plugin/gradle/JetifierPlugin.kt
new file mode 100644
index 0000000..b63bc9d
--- /dev/null
+++ b/jetifier/jetifier/gradle-plugin/src/main/kotlin/android/support/tools/jetifier/plugin/gradle/JetifierPlugin.kt
@@ -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.support.tools.jetifier.plugin.gradle
+
+import org.gradle.api.Plugin
+import org.gradle.api.Project
+
+/**
+ * This servers as the main entry point of this plugin and registers the extension object.
+ */
+open class JetifierPlugin : Plugin<Project>  {
+
+    companion object {
+        const val GROOVY_OBJECT_NAME : String = "jetifier"
+    }
+
+    override fun apply(project: Project) {
+        project.getExtensions().create(GROOVY_OBJECT_NAME, JetifierExtension::class.java, project)
+    }
+
+}
\ No newline at end of file
diff --git a/jetifier/jetifier/gradle-plugin/src/main/kotlin/android/support/tools/jetifier/plugin/gradle/JetifierTask.kt b/jetifier/jetifier/gradle-plugin/src/main/kotlin/android/support/tools/jetifier/plugin/gradle/JetifierTask.kt
new file mode 100644
index 0000000..d518f7b
--- /dev/null
+++ b/jetifier/jetifier/gradle-plugin/src/main/kotlin/android/support/tools/jetifier/plugin/gradle/JetifierTask.kt
@@ -0,0 +1,80 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *       http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR 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.tools.jetifier.plugin.gradle
+
+import android.support.tools.jetifier.core.Processor
+import android.support.tools.jetifier.core.config.ConfigParser
+import android.support.tools.jetifier.core.utils.Log
+import org.gradle.api.DefaultTask
+import org.gradle.api.file.FileCollection
+import org.gradle.api.tasks.InputFiles
+import org.gradle.api.tasks.OutputFiles
+import org.gradle.api.tasks.TaskAction
+import java.io.File
+
+
+/**
+ * The jetifier task that is run by gradle.
+ */
+open class JetifierTask : DefaultTask() {
+
+    companion object {
+        const val OUTPUT_DIR_APPENDIX = "jetifier"
+        const val GROUP_ID = "Pre-build"
+        // TODO: Get back to this once the name of the library is decided.
+        const val DESCRIPTION = "Rewrites input libraries to run with jetpack"
+    }
+
+    private var inputFiles = project.files()
+    private val outputDir = File(project.buildDir, OUTPUT_DIR_APPENDIX)
+
+    override fun getGroup() = GROUP_ID
+
+    override fun getDescription() = DESCRIPTION
+
+    fun addFilesToProcess(files: FileCollection) {
+        inputFiles = project.files(inputFiles.files.plus(files))
+    }
+
+    @InputFiles
+    fun getInputFiles(): FileCollection {
+        return inputFiles
+    }
+
+    @OutputFiles
+    fun getOutputFiles(): FileCollection {
+        return project.files(inputFiles.map { File(outputDir, it.name) }.toList())
+    }
+
+    @TaskAction
+    @Throws(Exception::class)
+    fun run() {
+        // Hook to the gradle logger
+        Log.logConsumer = JetifierLoggerAdapter(logger)
+
+        val config = ConfigParser.loadDefaultConfig()
+                ?: throw RuntimeException("Failed to load the default config!")
+
+        val processor = Processor(config)
+
+        for(inputFile in inputFiles) {
+            val inputPath = inputFile.toPath()
+            processor.transform(listOf(inputPath), outputDir.toPath())
+        }
+    }
+
+}
\ No newline at end of file
diff --git a/jetifier/jetifier/gradle-plugin/src/main/resources/META-INF/gradle-plugins/android.jetifier.properties b/jetifier/jetifier/gradle-plugin/src/main/resources/META-INF/gradle-plugins/android.jetifier.properties
new file mode 100644
index 0000000..7ee7e73
--- /dev/null
+++ b/jetifier/jetifier/gradle-plugin/src/main/resources/META-INF/gradle-plugins/android.jetifier.properties
@@ -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
+#
+
+implementation-class=android.support.tools.jetifier.plugin.gradle.JetifierPlugin
\ No newline at end of file
diff --git a/jetifier/jetifier/gradle/wrapper/gradle-wrapper.jar b/jetifier/jetifier/gradle/wrapper/gradle-wrapper.jar
new file mode 100644
index 0000000..2411732
--- /dev/null
+++ b/jetifier/jetifier/gradle/wrapper/gradle-wrapper.jar
Binary files differ
diff --git a/jetifier/jetifier/gradle/wrapper/gradle-wrapper.properties b/jetifier/jetifier/gradle/wrapper/gradle-wrapper.properties
new file mode 100644
index 0000000..2d19ff3
--- /dev/null
+++ b/jetifier/jetifier/gradle/wrapper/gradle-wrapper.properties
@@ -0,0 +1,5 @@
+distributionBase=GRADLE_USER_HOME
+distributionPath=wrapper/dists
+zipStoreBase=GRADLE_USER_HOME
+zipStorePath=wrapper/dists
+distributionUrl=../../../../../../tools/external/gradle/gradle-4.1-bin.zip
diff --git a/jetifier/jetifier/gradlew b/jetifier/jetifier/gradlew
new file mode 100755
index 0000000..04fca86
--- /dev/null
+++ b/jetifier/jetifier/gradlew
@@ -0,0 +1,172 @@
+#!/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
+save ( ) {
+    for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done
+    echo " "
+}
+APP_ARGS=$(save "$@")
+
+# 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 runTransform from Finder on Mac, the cwd is wrong
+if [ "$(uname)" = "Darwin" ] && [ "$HOME" = "$PWD" ]; then
+  cd "$(dirname "$0")"
+fi
+
+exec "$JAVACMD" "$@"
diff --git a/jetifier/jetifier/gradlew.bat b/jetifier/jetifier/gradlew.bat
new file mode 100644
index 0000000..e95643d
--- /dev/null
+++ b/jetifier/jetifier/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/jetifier/jetifier/preprocessor/build.gradle b/jetifier/jetifier/preprocessor/build.gradle
new file mode 100644
index 0000000..b688a9d
--- /dev/null
+++ b/jetifier/jetifier/preprocessor/build.gradle
@@ -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
+ */
+
+version '1.0'
+
+apply plugin: "application"
+
+mainClassName = "android.support.tools.jetifier.preprocessor.MainKt"
+
+dependencies {
+    compile project(':core')
+    compile group: 'commons-cli', name: 'commons-cli', version: '1.3.1'
+}
\ No newline at end of file
diff --git a/jetifier/jetifier/preprocessor/src/main/kotlin/android/support/tools/jetifier/preprocessor/Main.kt b/jetifier/jetifier/preprocessor/src/main/kotlin/android/support/tools/jetifier/preprocessor/Main.kt
new file mode 100644
index 0000000..2313115
--- /dev/null
+++ b/jetifier/jetifier/preprocessor/src/main/kotlin/android/support/tools/jetifier/preprocessor/Main.kt
@@ -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.support.tools.jetifier.preprocessor
+
+import android.support.tools.jetifier.core.archive.Archive
+import android.support.tools.jetifier.core.config.Config
+import android.support.tools.jetifier.core.config.ConfigParser
+import android.support.tools.jetifier.core.map.LibraryMapGenerator
+import android.support.tools.jetifier.core.utils.Log
+import org.apache.commons.cli.*
+import java.nio.file.Path
+
+import java.nio.file.Paths
+
+class Main {
+
+    companion object {
+        const val TAG = "Main"
+        const val TOOL_NAME = "preprocessor"
+
+        val OPTIONS = Options()
+        val OPTION_INPUT_LIBS = createOption("i", "Input libraries paths", multiple = true)
+        val OPTION_INPUT_CONFIG = createOption("c", "Input config path")
+        val OPTION_OUTPUT_CONFIG = createOption("o", "Output config path")
+        val OPTION_LOG_LEVEL = createOption("l", "Logging level. debug, verbose, default",
+            isRequired = false)
+
+        private fun createOption(argName: String,
+                                 desc: String,
+                                 isRequired: Boolean = true,
+                                 multiple: Boolean = false) : Option {
+            val op = Option(argName, true, desc)
+            op.isRequired = isRequired
+            if (multiple) {
+                op.args = Option.UNLIMITED_VALUES
+            }
+            OPTIONS.addOption(op)
+            return op
+        }
+    }
+
+    fun run(args : Array<String>) {
+        val cmd = parseCmdLine(args)
+        if (cmd == null) {
+            System.exit(1)
+            return
+        }
+
+        Log.setLevel(cmd.getOptionValue(OPTION_LOG_LEVEL.opt))
+
+        val inputLibraries = cmd.getOptionValues(OPTION_INPUT_LIBS.opt).map { Paths.get(it) }
+        val inputConfigPath = Paths.get(cmd.getOptionValue(OPTION_INPUT_CONFIG.opt))
+        val outputConfigPath = Paths.get(cmd.getOptionValue(OPTION_OUTPUT_CONFIG.opt))
+
+        val config = ConfigParser.loadFromFile(inputConfigPath)
+        if (config == null) {
+            System.exit(1)
+            return
+        }
+
+        generateMapping(config, inputLibraries, outputConfigPath)
+    }
+
+    private fun parseCmdLine(args : Array<String>) : CommandLine? {
+        try {
+            return DefaultParser().parse(OPTIONS, args)
+        } catch (e: ParseException) {
+            Log.e(TAG, e.message.orEmpty())
+            HelpFormatter().printHelp(TOOL_NAME, OPTIONS)
+        }
+        return null
+    }
+
+    private fun generateMapping(config: Config, inputLibraries: List<Path>, outputConfigPath: Path) {
+        val mapper = LibraryMapGenerator(config)
+        inputLibraries.forEach {
+            val library = Archive.Builder.extract(it)
+            mapper.scanLibrary(library)
+        }
+
+        val map = mapper.generateMap()
+        val newConfig = config.setNewMap(map)
+        ConfigParser.writeToFile(newConfig, outputConfigPath)
+    }
+
+}
+
+
+fun main(args : Array<String>) {
+    Main().run(args)
+}
\ No newline at end of file
diff --git a/jetifier/jetifier/settings.gradle b/jetifier/jetifier/settings.gradle
new file mode 100644
index 0000000..7d13b73
--- /dev/null
+++ b/jetifier/jetifier/settings.gradle
@@ -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
+ */
+
+rootProject.name = 'jetifier'
+include 'core'
+include 'gradle-plugin'
+include 'standalone'
+include 'preprocessor'
+
diff --git a/jetifier/jetifier/standalone/build.gradle b/jetifier/jetifier/standalone/build.gradle
new file mode 100644
index 0000000..8814d50
--- /dev/null
+++ b/jetifier/jetifier/standalone/build.gradle
@@ -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
+ */
+
+version '1.0'
+
+apply plugin: "application"
+
+mainClassName = "android.support.tools.jetifier.standalone.MainKt"
+
+dependencies {
+    compile project(':core')
+    compile group: 'commons-cli', name: 'commons-cli', version: '1.3.1'
+}
+
diff --git a/jetifier/jetifier/standalone/src/main/kotlin/android/support/tools/jetifier/standalone/Main.kt b/jetifier/jetifier/standalone/src/main/kotlin/android/support/tools/jetifier/standalone/Main.kt
new file mode 100644
index 0000000..70cf52d
--- /dev/null
+++ b/jetifier/jetifier/standalone/src/main/kotlin/android/support/tools/jetifier/standalone/Main.kt
@@ -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.support.tools.jetifier.standalone
+
+import android.support.tools.jetifier.core.Processor
+import android.support.tools.jetifier.core.config.Config
+import android.support.tools.jetifier.core.config.ConfigParser
+import android.support.tools.jetifier.core.utils.Log
+import org.apache.commons.cli.*
+
+import java.nio.file.Paths
+
+class Main {
+
+    companion object {
+        const val TAG = "Main"
+        const val TOOL_NAME = "standalone"
+
+        val OPTIONS = Options()
+        val OPTION_INPUT = createOption("i", "Input libraries paths", multiple = true)
+        val OPTION_OUTPUT = createOption("o", "Output config path")
+        val OPTION_CONFIG = createOption("c", "Input config path", isRequired = false)
+        val OPTION_LOG_LEVEL = createOption("l", "Logging level. debug, verbose, default",
+            isRequired = false)
+
+        private fun createOption(argName: String,
+                                 desc: String,
+                                 isRequired: Boolean = true,
+                                 multiple: Boolean = false) : Option {
+            val op = Option(argName, true, desc)
+            op.isRequired = isRequired
+            if (multiple) {
+                op.args = Option.UNLIMITED_VALUES
+            }
+            OPTIONS.addOption(op)
+            return op
+        }
+    }
+
+    fun run(args : Array<String>) {
+        val cmd = parseCmdLine(args)
+        if (cmd == null) {
+            System.exit(1)
+            return
+        }
+
+        Log.setLevel(cmd.getOptionValue(OPTION_LOG_LEVEL.opt))
+
+        val inputLibraries = cmd.getOptionValues(OPTION_INPUT.opt).map { Paths.get(it) }
+        val outputPath = Paths.get(cmd.getOptionValue(OPTION_OUTPUT.opt))
+
+        val config : Config?
+        if (cmd.hasOption(OPTION_CONFIG.opt)) {
+            val configPath = Paths.get(cmd.getOptionValue(OPTION_CONFIG.opt))
+            config = ConfigParser.loadFromFile(configPath)
+        } else {
+            config = ConfigParser.loadDefaultConfig()
+        }
+
+        if (config == null) {
+            Log.e(TAG, "Failed to load the config file")
+            System.exit(1)
+            return
+        }
+
+        val processor = Processor(config)
+        processor.transform(inputLibraries, outputPath)
+    }
+
+    private fun parseCmdLine(args : Array<String>) : CommandLine? {
+        try {
+            return DefaultParser().parse(OPTIONS, args)
+        } catch (e: ParseException) {
+            Log.e(TAG, e.message.orEmpty())
+            HelpFormatter().printHelp(TOOL_NAME, OPTIONS)
+        }
+        return null
+    }
+
+}
+
+
+fun main(args : Array<String>) {
+    Main().run(args)
+}
\ No newline at end of file
diff --git a/samples/SupportSliceDemos/build.gradle b/samples/SupportSliceDemos/build.gradle
new file mode 100644
index 0000000..bd6cd01
--- /dev/null
+++ b/samples/SupportSliceDemos/build.gradle
@@ -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.
+ */
+
+apply plugin: 'com.android.application'
+
+dependencies {
+    implementation project(':slices-view')
+    implementation project(':slices-builders')
+    implementation project(':design')
+    implementation project(':appcompat-v7')
+    implementation project(':cardview-v7')
+}
+
+android {
+    compileSdkVersion project.ext.currentSdk
+
+    defaultConfig {
+        applicationId "com.example.androidx.slice.demos"
+        minSdkVersion 27
+        targetSdkVersion project.ext.currentSdk
+    }
+
+    signingConfigs {
+        debug {
+            // Use a local debug keystore to avoid build server issues.
+            storeFile project.rootProject.init.debugKeystore
+        }
+    }
+
+    lintOptions {
+        abortOnError true
+        disable "SetTextI18n", "AppCompatResource", "WrongConstant", "AllowBackup",
+                "GoogleAppIndexingWarning", "AlwaysShowAction"
+    }
+    compileOptions {
+        targetCompatibility 1.8
+        sourceCompatibility 1.8
+    }
+}
diff --git a/samples/SupportSliceDemos/src/main/AndroidManifest.xml b/samples/SupportSliceDemos/src/main/AndroidManifest.xml
new file mode 100644
index 0000000..1fd990d
--- /dev/null
+++ b/samples/SupportSliceDemos/src/main/AndroidManifest.xml
@@ -0,0 +1,48 @@
+<?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.androidx.slice.demos">
+
+    <uses-sdk tools:overrideLibrary="androidx.app.slice.view, androidx.app.slice.builders, androidx.app.slice.core" />
+
+    <uses-permission android:name="android.permission.BIND_SLICE" />
+
+    <application
+        android:allowBackup="true"
+        android:icon="@mipmap/ic_launcher"
+        android:label="@string/app_name"
+        android:roundIcon="@mipmap/ic_launcher_round"
+        android:supportsRtl="true"
+        android:theme="@style/AppTheme">
+        <activity
+            android:name=".SliceBrowser"
+            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>
+
+        <provider android:authorities="com.example.androidx.slice.demos"
+                  android:name=".SampleSliceProvider"
+                  android:grantUriPermissions="true" />
+    </application>
+
+</manifest>
diff --git a/samples/SupportSliceDemos/src/main/java/com/example/androidx/slice/demos/SampleSliceProvider.java b/samples/SupportSliceDemos/src/main/java/com/example/androidx/slice/demos/SampleSliceProvider.java
new file mode 100644
index 0000000..0157cf7
--- /dev/null
+++ b/samples/SupportSliceDemos/src/main/java/com/example/androidx/slice/demos/SampleSliceProvider.java
@@ -0,0 +1,72 @@
+/*
+ * 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.androidx.slice.demos;
+
+import android.app.slice.Slice;
+import android.app.slice.SliceProvider;
+import android.graphics.drawable.Icon;
+import android.net.Uri;
+import android.support.annotation.RequiresApi;
+import android.text.format.DateUtils;
+
+import androidx.app.slice.builders.MessagingSliceBuilder;
+
+/**
+ * Examples of using slice template builders.
+ */
+@RequiresApi(api = 28)
+public class SampleSliceProvider extends SliceProvider {
+    public static final Uri MESSAGE =
+            Uri.parse("content://com.example.androidx.slice.demos/message");
+
+    @Override
+    public boolean onCreate() {
+        return true;
+    }
+
+    @Override
+    public Slice onBindSlice(Uri sliceUri) {
+        String path = sliceUri.getPath();
+        switch (path) {
+            case "/message":
+                return createMessagingSlice(sliceUri);
+        }
+        throw new IllegalArgumentException("Unknown uri " + sliceUri);
+    }
+
+    private Slice createMessagingSlice(Uri sliceUri) {
+        // TODO: Remote input.
+        return new MessagingSliceBuilder(sliceUri)
+                .startMessage()
+                        .addText("yo home \uD83C\uDF55, I emailed you the info")
+                        .addTimestamp(System.currentTimeMillis() - 20 * DateUtils.MINUTE_IN_MILLIS)
+                        .addSource(Icon.createWithResource(getContext(), R.drawable.mady))
+                        .endMessage()
+                .startMessage()
+                        .addText("just bought my tickets")
+                        .addTimestamp(System.currentTimeMillis() - 10 * DateUtils.MINUTE_IN_MILLIS)
+                        .endMessage()
+                .startMessage()
+                        .addText("yay! can't wait for getContext() weekend!\n"
+                                + "\uD83D\uDE00")
+                        .addTimestamp(System.currentTimeMillis() - 5 * DateUtils.MINUTE_IN_MILLIS)
+                        .addSource(Icon.createWithResource(getContext(), R.drawable.mady))
+                        .endMessage()
+                .build();
+
+    }
+}
diff --git a/samples/SupportSliceDemos/src/main/java/com/example/androidx/slice/demos/SliceBrowser.java b/samples/SupportSliceDemos/src/main/java/com/example/androidx/slice/demos/SliceBrowser.java
new file mode 100644
index 0000000..8986630
--- /dev/null
+++ b/samples/SupportSliceDemos/src/main/java/com/example/androidx/slice/demos/SliceBrowser.java
@@ -0,0 +1,220 @@
+/*
+ * 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.androidx.slice.demos;
+
+import android.app.Activity;
+import android.content.ContentResolver;
+import android.content.pm.ActivityInfo;
+import android.content.pm.PackageInfo;
+import android.content.pm.PackageManager;
+import android.database.Cursor;
+import android.database.MatrixCursor;
+import android.net.Uri;
+import android.os.Bundle;
+import android.provider.BaseColumns;
+import android.support.annotation.RequiresApi;
+import android.util.ArrayMap;
+import android.util.Log;
+import android.view.Menu;
+import android.view.MenuItem;
+import android.view.SubMenu;
+import android.view.ViewGroup;
+import android.widget.CursorAdapter;
+import android.widget.SearchView;
+import android.widget.SimpleCursorAdapter;
+import android.widget.Toolbar;
+
+import java.util.ArrayList;
+import java.util.Comparator;
+import java.util.List;
+
+import androidx.app.slice.widget.SliceView;
+
+/**
+ * Example use of SliceView. Uses a search bar to select/auto-complete a slice uri which is
+ * then displayed in the selected mode with SliceView.
+ */
+@RequiresApi(api = 28)
+public class SliceBrowser extends Activity {
+
+    private static final String TAG = "SlicePresenter";
+
+    private static final String SLICE_METADATA_KEY = "android.metadata.SLICE_URI";
+
+    private ArrayList<Uri> mSliceUris = new ArrayList<Uri>();
+    private int mSelectedMode;
+    private ViewGroup mContainer;
+    private SearchView mSearchView;
+    private SimpleCursorAdapter mAdapter;
+    private SubMenu mTypeMenu;
+
+    @Override
+    public void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+        setContentView(R.layout.activity_layout);
+
+        Toolbar toolbar = findViewById(R.id.search_toolbar);
+        setActionBar(toolbar);
+
+        // Shows the slice
+        mContainer = findViewById(R.id.slice_preview);
+        mSearchView = findViewById(R.id.search_view);
+
+        final String[] from = new String[]{"uri"};
+        final int[] to = new int[]{android.R.id.text1};
+        mAdapter = new SimpleCursorAdapter(this, android.R.layout.simple_list_item_1,
+                null, from, to, CursorAdapter.FLAG_REGISTER_CONTENT_OBSERVER);
+        mSearchView.setSuggestionsAdapter(mAdapter);
+        mSearchView.setIconifiedByDefault(false);
+        mSearchView.setOnSuggestionListener(new SearchView.OnSuggestionListener() {
+            @Override
+            public boolean onSuggestionClick(int position) {
+                mSearchView.setQuery(((Cursor) mAdapter.getItem(position)).getString(1), true);
+                return true;
+            }
+
+            @Override
+            public boolean onSuggestionSelect(int position) {
+                mSearchView.setQuery(((Cursor) mAdapter.getItem(position)).getString(1), true);
+                return true;
+            }
+        });
+        mSearchView.setOnQueryTextListener(new SearchView.OnQueryTextListener() {
+            @Override
+            public boolean onQueryTextSubmit(String s) {
+                addSlice(Uri.parse(s));
+                mSearchView.clearFocus();
+                return false;
+            }
+
+            @Override
+            public boolean onQueryTextChange(String s) {
+                populateAdapter(s);
+                return false;
+            }
+        });
+
+        mSelectedMode = (savedInstanceState != null)
+                ? savedInstanceState.getInt("SELECTED_MODE", SliceView.MODE_SHORTCUT)
+                : SliceView.MODE_SHORTCUT;
+        if (savedInstanceState != null) {
+            mSearchView.setQuery(savedInstanceState.getString("SELECTED_QUERY"), true);
+        }
+
+        // TODO: Listen for changes.
+        updateAvailableSlices();
+    }
+
+    @Override
+    public boolean onCreateOptionsMenu(Menu menu) {
+        mTypeMenu = menu.addSubMenu("Type");
+        mTypeMenu.setIcon(R.drawable.ic_shortcut);
+        mTypeMenu.getItem().setShowAsAction(MenuItem.SHOW_AS_ACTION_ALWAYS);
+        mTypeMenu.add("Shortcut");
+        mTypeMenu.add("Small");
+        mTypeMenu.add("Large");
+        super.onCreateOptionsMenu(menu);
+        return true;
+    }
+
+    @Override
+    public boolean onOptionsItemSelected(MenuItem item) {
+        switch (item.getTitle().toString()) {
+            case "Shortcut":
+                mTypeMenu.setIcon(R.drawable.ic_shortcut);
+                mSelectedMode = SliceView.MODE_SHORTCUT;
+                updateSliceModes();
+                return true;
+            case "Small":
+                mTypeMenu.setIcon(R.drawable.ic_small);
+                mSelectedMode = SliceView.MODE_SMALL;
+                updateSliceModes();
+                return true;
+            case "Large":
+                mTypeMenu.setIcon(R.drawable.ic_large);
+                mSelectedMode = SliceView.MODE_LARGE;
+                updateSliceModes();
+                return true;
+        }
+        return super.onOptionsItemSelected(item);
+    }
+
+    @Override
+    protected void onSaveInstanceState(Bundle outState) {
+        outState.putInt("SELECTED_MODE", mSelectedMode);
+        outState.putString("SELECTED_QUERY", mSearchView.getQuery().toString());
+    }
+
+    private void updateAvailableSlices() {
+        mSliceUris.clear();
+        List<PackageInfo> packageInfos = getPackageManager()
+                .getInstalledPackages(PackageManager.GET_ACTIVITIES | PackageManager.GET_META_DATA);
+        for (PackageInfo pi : packageInfos) {
+            ActivityInfo[] activityInfos = pi.activities;
+            if (activityInfos != null) {
+                for (ActivityInfo ai : activityInfos) {
+                    if (ai.metaData != null) {
+                        String sliceUri = ai.metaData.getString(SLICE_METADATA_KEY);
+                        if (sliceUri != null) {
+                            mSliceUris.add(Uri.parse(sliceUri));
+                        }
+                    }
+                }
+            }
+        }
+        mSliceUris.add(SampleSliceProvider.MESSAGE);
+        populateAdapter(String.valueOf(mSearchView.getQuery()));
+    }
+
+    private void addSlice(Uri uri) {
+        if (ContentResolver.SCHEME_CONTENT.equals(uri.getScheme())) {
+            SliceView v = new SliceView(getApplicationContext());
+            v.setTag(uri);
+            mContainer.removeAllViews();
+            mContainer.addView(v);
+            v.setMode(mSelectedMode);
+            v.setSlice(uri);
+        } else {
+            Log.w(TAG, "Invalid uri, skipping slice: " + uri);
+        }
+    }
+
+    private void updateSliceModes() {
+        final int count = mContainer.getChildCount();
+        for (int i = 0; i < count; i++) {
+            ((SliceView) mContainer.getChildAt(i)).setMode(mSelectedMode);
+        }
+    }
+
+    private void populateAdapter(String query) {
+        final MatrixCursor c = new MatrixCursor(new String[]{BaseColumns._ID, "uri"});
+        ArrayMap<String, Integer> ranking = new ArrayMap<>();
+        ArrayList<String> suggestions = new ArrayList();
+        mSliceUris.forEach(uri -> {
+            String uriString = uri.toString();
+            if (uriString.contains(query)) {
+                ranking.put(uriString, uriString.indexOf(query));
+                suggestions.add(uriString);
+            }
+        });
+        suggestions.sort(Comparator.comparingInt(ranking::get));
+        for (int i = 0; i < suggestions.size(); i++) {
+            c.addRow(new Object[]{i, suggestions.get(i)});
+        }
+        mAdapter.changeCursor(c);
+    }
+}
diff --git a/samples/SupportSliceDemos/src/main/res/drawable/ic_large.xml b/samples/SupportSliceDemos/src/main/res/drawable/ic_large.xml
new file mode 100644
index 0000000..b8042a8
--- /dev/null
+++ b/samples/SupportSliceDemos/src/main/res/drawable/ic_large.xml
@@ -0,0 +1,28 @@
+<!--
+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.
+-->
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+        android:width="48.0dp"
+        android:height="48.0dp"
+        android:viewportWidth="48.0"
+        android:viewportHeight="48.0">
+    <path
+        android:fillColor="#FF000000"
+        android:pathData="M34.0,2.02L14.0,2.0c-2.21,0.0 -4.0,1.79 -4.0,4.0l0.0,36.0c0.0,2.21 1.79,4.0 4.0,4.0l20.0,0.0c2.21,0.0 4.0,-1.79 4.0,-4.0L38.0,6.0c0.0,-2.21 -1.79,-3.98 -4.0,-3.98zM34.0,38.0L14.0,38.0L14.0,10.0l20.0,0.0l0.0,28.0z"/>
+    <path
+        android:strokeColor="#FF000000"
+        android:strokeWidth="2"
+        android:pathData="M16,18 l16,0 l0,10 l-16,0z"/>
+</vector>
diff --git a/samples/SupportSliceDemos/src/main/res/drawable/ic_shortcut.xml b/samples/SupportSliceDemos/src/main/res/drawable/ic_shortcut.xml
new file mode 100644
index 0000000..4b5485d
--- /dev/null
+++ b/samples/SupportSliceDemos/src/main/res/drawable/ic_shortcut.xml
@@ -0,0 +1,34 @@
+<!--
+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.
+-->
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+    android:height="48.0dp"
+    android:viewportHeight="48.0"
+    android:viewportWidth="48.0"
+    android:width="48.0dp">
+    <path
+        android:fillColor="#e2e2e2"
+        android:pathData="M24.0,24.0m-19.0,0.0a19.0,19.0 0.0,1.0 1.0,38.0 0.0a19.0,19.0 0.0,1.0 1.0,-38.0 0.0"/>
+    <group
+        android:scaleX=".7"
+        android:scaleY=".7"
+        android:translateX="7.2"
+        android:translateY="7.2">
+
+        <path
+            android:fillColor="#ff000000"
+            android:pathData="M12.0,36.0c0.0,1.0 0.9,2.0 2.0,2.0l2.0,0.0l0.0,7.0c0.0,1.66 1.34,3.0 3.0,3.0s3.0,-1.34 3.0,-3.0l0.0,-7.0l4.0,0.0l0.0,7.0c0.0,1.66 1.34,3.0 3.0,3.0s3.0,-1.34 3.0,-3.0l0.0,-7.0l2.0,0.0c1.1,0.0 2.0,-0.9 2.0,-2.0L36.0,16.0L12.0,16.0l0.0,20.0zM7.0,16.0c-1.66,0.0 -3.0,1.34 -3.0,3.0l0.0,14.0c0.0,1.66 1.34,3.0 3.0,3.0s3.0,-1.34 3.0,-3.0L10.0,19.0c0.0,-1.66 -1.34,-3.0 -3.0,-3.0zm34.0,0.0c-1.66,0.0 -3.0,1.34 -3.0,3.0l0.0,14.0c0.0,1.66 1.34,3.0 3.0,3.0s3.0,-1.34 3.0,-3.0L44.0,19.0c0.0,-1.66 -1.34,-3.0 -3.0,-3.0zM31.06,4.32l2.61,-2.61c0.39,-0.3 0.39,-1.02 0.0,-1.41 -0.39,-0.39 -1.02,-0.39 -1.41,0.0L29.3,3.25C27.7,2.46 25.91,2.0 24.0,2.0c-1.92,0.0 -3.7,0.46 -5.33,1.26L15.0,0.29c-0.39,-0.39 -1.02,-0.39 -1.41,0.0 -0.3,0.39 -0.39,1.02 0.0,1.41l2.62,2.62C13.94,6.51 12.0,10.03 12.0,14.0l24.0,0.0c0.0,-3.98 -1.95,-7.5 -4.94,-9.68zM20.0,10.0l-2.0,0.0L18.0,8.0l2.0,0.0l0.0,2.0zm10.0,0.0l-2.0,0.0L28.0,8.0l2.0,0.0l0.0,2.0z"/>
+    </group>
+</vector>
diff --git a/samples/SupportSliceDemos/src/main/res/drawable/ic_small.xml b/samples/SupportSliceDemos/src/main/res/drawable/ic_small.xml
new file mode 100644
index 0000000..b35e1c1
--- /dev/null
+++ b/samples/SupportSliceDemos/src/main/res/drawable/ic_small.xml
@@ -0,0 +1,27 @@
+<!--
+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.
+-->
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+        android:width="48.0dp"
+        android:height="48.0dp"
+        android:viewportWidth="48.0"
+        android:viewportHeight="48.0">
+    <path
+        android:fillColor="#FF000000"
+        android:pathData="M34.0,2.02L14.0,2.0c-2.21,0.0 -4.0,1.79 -4.0,4.0l0.0,36.0c0.0,2.21 1.79,4.0 4.0,4.0l20.0,0.0c2.21,0.0 4.0,-1.79 4.0,-4.0L38.0,6.0c0.0,-2.21 -1.79,-3.98 -4.0,-3.98zM34.0,38.0L14.0,38.0L14.0,10.0l20.0,0.0l0.0,28.0z"/>
+    <path
+        android:fillColor="#FF000000"
+        android:pathData="M16,18 l16,0 l0,4 l-16,0z"/>
+</vector>
diff --git a/samples/SupportSliceDemos/src/main/res/drawable/mady.jpg b/samples/SupportSliceDemos/src/main/res/drawable/mady.jpg
new file mode 100644
index 0000000..8b61f1b
--- /dev/null
+++ b/samples/SupportSliceDemos/src/main/res/drawable/mady.jpg
Binary files differ
diff --git a/samples/SupportSliceDemos/src/main/res/layout/activity_demo.xml b/samples/SupportSliceDemos/src/main/res/layout/activity_demo.xml
new file mode 100644
index 0000000..f557f40
--- /dev/null
+++ b/samples/SupportSliceDemos/src/main/res/layout/activity_demo.xml
@@ -0,0 +1,40 @@
+<?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"
+    xmlns:tools="http://schemas.android.com/tools"
+    android:orientation="vertical"
+    android:layout_width="match_parent"
+    android:layout_height="match_parent"
+    tools:context="com.example.android.support.content.demos.ContentPagerDemoActivity">
+
+    <android.support.v7.widget.Toolbar
+        android:id="@+id/toolbar"
+        android:layout_width="match_parent"
+        android:layout_height="?attr/actionBarSize"
+        android:background="?attr/colorPrimary"
+        android:theme="@style/AppTheme.AppBarOverlay"
+        app:popupTheme="@style/AppTheme.PopupOverlay"/>
+
+    <android.support.v7.widget.RecyclerView
+        android:id="@+id/list"
+        android:layout_width="match_parent"
+        android:layout_height="match_parent"/>
+
+</LinearLayout>
diff --git a/samples/SupportSliceDemos/src/main/res/layout/activity_layout.xml b/samples/SupportSliceDemos/src/main/res/layout/activity_layout.xml
new file mode 100644
index 0000000..833414c
--- /dev/null
+++ b/samples/SupportSliceDemos/src/main/res/layout/activity_layout.xml
@@ -0,0 +1,75 @@
+<?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.
+-->
+
+<LinearLayout 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:gravity="top"
+    android:orientation="vertical" >
+
+    <FrameLayout
+        android:id="@+id/search_bar_container"
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        android:background="#f2f2f2">
+        <android.support.v7.widget.CardView
+            android:id="@+id/search_bar"
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:layout_margin="8dp"
+            app:cardCornerRadius="2dp"
+            app:cardBackgroundColor="?android:attr/colorBackground"
+            app:cardElevation="2dp">
+            <Toolbar
+                android:id="@+id/search_toolbar"
+                android:layout_width="match_parent"
+                android:layout_height="48dp"
+                android:background="?android:attr/selectableItemBackground"
+                android:contentInsetStart="0dp"
+                android:contentInsetStartWithNavigation="0dp"
+                android:theme="?android:attr/actionBarTheme">
+                <SearchView
+                    android:id="@+id/search_view"
+                    android:layout_width="match_parent"
+                    android:layout_height="match_parent"
+                    android:iconifiedByDefault="false"
+                    android:imeOptions="actionSearch|flagNoExtractUi"
+                    android:queryHint="content://..."
+                    android:searchIcon="@null"/>
+            </Toolbar>
+        </android.support.v7.widget.CardView>
+    </FrameLayout>
+
+
+    <ScrollView
+        android:id="@+id/container"
+        android:layout_width="match_parent"
+        android:layout_height="0dp"
+        android:layout_weight="1" >
+
+        <FrameLayout
+            android:id="@+id/slice_preview"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:layout_margin="8dp"
+            android:orientation="vertical"
+            android:background="?android:attr/colorBackground"
+            android:elevation="10dp" />
+    </ScrollView>
+
+</LinearLayout>
diff --git a/samples/SupportSliceDemos/src/main/res/mipmap-hdpi/ic_launcher.png b/samples/SupportSliceDemos/src/main/res/mipmap-hdpi/ic_launcher.png
new file mode 100644
index 0000000..cde69bc
--- /dev/null
+++ b/samples/SupportSliceDemos/src/main/res/mipmap-hdpi/ic_launcher.png
Binary files differ
diff --git a/samples/SupportSliceDemos/src/main/res/mipmap-hdpi/ic_launcher_round.png b/samples/SupportSliceDemos/src/main/res/mipmap-hdpi/ic_launcher_round.png
new file mode 100644
index 0000000..9a078e3
--- /dev/null
+++ b/samples/SupportSliceDemos/src/main/res/mipmap-hdpi/ic_launcher_round.png
Binary files differ
diff --git a/samples/SupportSliceDemos/src/main/res/mipmap-mdpi/ic_launcher.png b/samples/SupportSliceDemos/src/main/res/mipmap-mdpi/ic_launcher.png
new file mode 100644
index 0000000..c133a0c
--- /dev/null
+++ b/samples/SupportSliceDemos/src/main/res/mipmap-mdpi/ic_launcher.png
Binary files differ
diff --git a/samples/SupportSliceDemos/src/main/res/mipmap-mdpi/ic_launcher_round.png b/samples/SupportSliceDemos/src/main/res/mipmap-mdpi/ic_launcher_round.png
new file mode 100644
index 0000000..efc028a
--- /dev/null
+++ b/samples/SupportSliceDemos/src/main/res/mipmap-mdpi/ic_launcher_round.png
Binary files differ
diff --git a/samples/SupportSliceDemos/src/main/res/mipmap-xhdpi/ic_launcher.png b/samples/SupportSliceDemos/src/main/res/mipmap-xhdpi/ic_launcher.png
new file mode 100644
index 0000000..bfa42f0
--- /dev/null
+++ b/samples/SupportSliceDemos/src/main/res/mipmap-xhdpi/ic_launcher.png
Binary files differ
diff --git a/samples/SupportSliceDemos/src/main/res/mipmap-xhdpi/ic_launcher_round.png b/samples/SupportSliceDemos/src/main/res/mipmap-xhdpi/ic_launcher_round.png
new file mode 100644
index 0000000..3af2608
--- /dev/null
+++ b/samples/SupportSliceDemos/src/main/res/mipmap-xhdpi/ic_launcher_round.png
Binary files differ
diff --git a/samples/SupportSliceDemos/src/main/res/mipmap-xxhdpi/ic_launcher.png b/samples/SupportSliceDemos/src/main/res/mipmap-xxhdpi/ic_launcher.png
new file mode 100644
index 0000000..324e72c
--- /dev/null
+++ b/samples/SupportSliceDemos/src/main/res/mipmap-xxhdpi/ic_launcher.png
Binary files differ
diff --git a/samples/SupportSliceDemos/src/main/res/mipmap-xxhdpi/ic_launcher_round.png b/samples/SupportSliceDemos/src/main/res/mipmap-xxhdpi/ic_launcher_round.png
new file mode 100644
index 0000000..9bec2e6
--- /dev/null
+++ b/samples/SupportSliceDemos/src/main/res/mipmap-xxhdpi/ic_launcher_round.png
Binary files differ
diff --git a/samples/SupportSliceDemos/src/main/res/mipmap-xxxhdpi/ic_launcher.png b/samples/SupportSliceDemos/src/main/res/mipmap-xxxhdpi/ic_launcher.png
new file mode 100644
index 0000000..aee44e1
--- /dev/null
+++ b/samples/SupportSliceDemos/src/main/res/mipmap-xxxhdpi/ic_launcher.png
Binary files differ
diff --git a/samples/SupportSliceDemos/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png b/samples/SupportSliceDemos/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png
new file mode 100644
index 0000000..34947cd
--- /dev/null
+++ b/samples/SupportSliceDemos/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png
Binary files differ
diff --git a/samples/SupportSliceDemos/src/main/res/values/colors.xml b/samples/SupportSliceDemos/src/main/res/values/colors.xml
new file mode 100644
index 0000000..86b4304
--- /dev/null
+++ b/samples/SupportSliceDemos/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/SupportSliceDemos/src/main/res/values/strings.xml b/samples/SupportSliceDemos/src/main/res/values/strings.xml
new file mode 100644
index 0000000..89583bd
--- /dev/null
+++ b/samples/SupportSliceDemos/src/main/res/values/strings.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 xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="app_name">Slice Browser</string>
+</resources>
diff --git a/samples/SupportSliceDemos/src/main/res/values/styles.xml b/samples/SupportSliceDemos/src/main/res/values/styles.xml
new file mode 100644
index 0000000..afb6bad
--- /dev/null
+++ b/samples/SupportSliceDemos/src/main/res/values/styles.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.
+  -->
+
+<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/settings.gradle b/settings.gradle
index 674e128..6f646fb 100644
--- a/settings.gradle
+++ b/settings.gradle
@@ -109,6 +109,15 @@
 include ':webkit'
 project(':webkit').projectDir = new File(rootDir, 'webkit')
 
+include ':slices-core'
+project(':slices-core').projectDir = new File(rootDir, 'slices/core')
+
+include ':slices-view'
+project(':slices-view').projectDir = new File(rootDir, 'slices/view')
+
+include ':slices-builders'
+project(':slices-builders').projectDir = new File(rootDir, 'slices/builders')
+
 /////////////////////////////
 //
 // Samples
@@ -162,6 +171,9 @@
 include ':support-emoji-demos'
 project(':support-emoji-demos').projectDir = new File(samplesRoot, 'SupportEmojiDemos')
 
+include ':support-slices-demos'
+project(':support-slices-demos').projectDir = new File(samplesRoot, 'SupportSliceDemos')
+
 /////////////////////////////
 //
 // Testing libraries
diff --git a/slices/OWNERS b/slices/OWNERS
new file mode 100644
index 0000000..921a517
--- /dev/null
+++ b/slices/OWNERS
@@ -0,0 +1,2 @@
+jmonk@google.com
+madym@google.com
diff --git a/slices/builders/build.gradle b/slices/builders/build.gradle
new file mode 100644
index 0000000..c6fcfc0
--- /dev/null
+++ b/slices/builders/build.gradle
@@ -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.
+ */
+
+plugins {
+    id("SupportAndroidLibraryPlugin")
+}
+
+dependencies {
+    implementation project(":slices-core")
+    implementation project(":support-annotations")
+}
+
+android {
+    defaultConfig {
+        minSdkVersion 28
+    }
+}
+
+supportLibrary {
+    name 'Slice builders'
+    publish false
+    inceptionYear '2017'
+    description "A set of builders to create templates using SliceProvider APIs"
+    java8Library true
+}
diff --git a/slices/builders/lint-baseline.xml b/slices/builders/lint-baseline.xml
new file mode 100644
index 0000000..2cadde1
--- /dev/null
+++ b/slices/builders/lint-baseline.xml
@@ -0,0 +1,4 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<issues format="4" by="lint 3.0.0">
+
+</issues>
diff --git a/slices/builders/src/main/AndroidManifest.xml b/slices/builders/src/main/AndroidManifest.xml
new file mode 100644
index 0000000..75e31f1
--- /dev/null
+++ b/slices/builders/src/main/AndroidManifest.xml
@@ -0,0 +1,19 @@
+<?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="androidx.app.slice.builders">
+    <uses-sdk android:minSdkVersion="28"/>
+</manifest>
diff --git a/slices/builders/src/main/java/androidx/app/slice/builders/MessagingSliceBuilder.java b/slices/builders/src/main/java/androidx/app/slice/builders/MessagingSliceBuilder.java
new file mode 100644
index 0000000..2918904
--- /dev/null
+++ b/slices/builders/src/main/java/androidx/app/slice/builders/MessagingSliceBuilder.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 androidx.app.slice.builders;
+
+import static android.support.annotation.RestrictTo.Scope.LIBRARY_GROUP;
+
+import android.app.slice.Slice;
+import android.graphics.drawable.Icon;
+import android.net.Uri;
+import android.support.annotation.NonNull;
+import android.support.annotation.RestrictTo;
+
+/**
+ * Builder to construct slice content in a messaging format.
+ */
+public class MessagingSliceBuilder extends TemplateSliceBuilder {
+
+    /**
+     * The maximum number of messages that will be retained in the Slice itself (the
+     * number displayed is up to the platform).
+     */
+    public static final int MAXIMUM_RETAINED_MESSAGES = 50;
+
+    public MessagingSliceBuilder(@NonNull Uri uri) {
+        super(uri);
+    }
+
+    /**
+     * Create a {@link MessageBuilder} that will be added to this slice when
+     * {@link MessageBuilder#finish()} is called.
+     * @return a new message builder
+     */
+    public MessageBuilder startMessage() {
+        return new MessageBuilder(this);
+    }
+
+    /**
+     * @hide
+     */
+    @RestrictTo(LIBRARY_GROUP)
+    @Override
+    public void apply(Slice.Builder builder) {
+    }
+
+    @Override
+    public void add(SubTemplateSliceBuilder builder) {
+        getBuilder().addSubSlice(builder.build());
+    }
+
+    /**
+     * Builder for adding a message to {@link MessagingSliceBuilder}.
+     */
+    public static final class MessageBuilder
+            extends SubTemplateSliceBuilder<MessagingSliceBuilder> {
+        /**
+         * @hide
+         */
+        @RestrictTo(RestrictTo.Scope.LIBRARY)
+        public MessageBuilder(MessagingSliceBuilder parent) {
+            super(parent.createChildBuilder(), parent);
+            getBuilder().addHints(Slice.HINT_MESSAGE);
+        }
+
+        /**
+         * Add the icon used to display contact in the messaging experience
+         */
+        public MessageBuilder addSource(Icon source) {
+            getBuilder().addIcon(source, Slice.HINT_SOURCE);
+            return this;
+        }
+
+        /**
+         * Add the text to be used for this message.
+         */
+        public MessageBuilder addText(CharSequence text) {
+            getBuilder().addText(text);
+            return this;
+        }
+
+        /**
+         * Add the time at which this message arrived in ms since Unix epoch
+         */
+        public MessageBuilder addTimestamp(long timestamp) {
+            getBuilder().addTimestamp(timestamp);
+            return this;
+        }
+
+        /**
+         * Complete the construction of this message and add it to the parent builder.
+         * @return the parent builder so construction can continue.
+         */
+        public MessagingSliceBuilder endMessage() {
+            return finish();
+        }
+    }
+}
diff --git a/slices/builders/src/main/java/androidx/app/slice/builders/TemplateSliceBuilder.java b/slices/builders/src/main/java/androidx/app/slice/builders/TemplateSliceBuilder.java
new file mode 100644
index 0000000..61fea7f
--- /dev/null
+++ b/slices/builders/src/main/java/androidx/app/slice/builders/TemplateSliceBuilder.java
@@ -0,0 +1,127 @@
+/*
+ * 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 androidx.app.slice.builders;
+
+
+import android.app.slice.Slice;
+import android.net.Uri;
+import android.support.annotation.RestrictTo;
+
+/**
+ * Base class of builders of various template types.
+ */
+public abstract class TemplateSliceBuilder {
+
+    private final Slice.Builder mSliceBuilder;
+
+    public TemplateSliceBuilder(Uri uri) {
+        mSliceBuilder = new Slice.Builder(uri);
+    }
+
+    /**
+     * @hide
+     */
+    @RestrictTo(RestrictTo.Scope.LIBRARY)
+    public Slice.Builder getBuilder() {
+        return mSliceBuilder;
+    }
+
+    /**
+     * @hide
+     */
+    @RestrictTo(RestrictTo.Scope.LIBRARY)
+    public Slice.Builder createChildBuilder() {
+        return new Slice.Builder(mSliceBuilder);
+    }
+
+    /**
+     * Construct the slice.
+     */
+    public Slice build() {
+        apply(mSliceBuilder);
+        return mSliceBuilder.build();
+    }
+
+    /**
+     * @hide
+     */
+    @RestrictTo(RestrictTo.Scope.LIBRARY)
+    public abstract void apply(Slice.Builder builder);
+
+    /**
+     * @hide
+     */
+    @RestrictTo(RestrictTo.Scope.LIBRARY)
+    public abstract void add(SubTemplateSliceBuilder builder);
+
+    /**
+     * Base class of builders for sub-slices of {@link TemplateSliceBuilder}s.
+     * @param <T> Type of parent
+     */
+    public abstract static class SubTemplateSliceBuilder<T extends TemplateSliceBuilder> {
+
+        private final Slice.Builder mBuilder;
+        private final T mParent;
+
+        /**
+         * @hide
+         */
+        @RestrictTo(RestrictTo.Scope.LIBRARY)
+        public SubTemplateSliceBuilder(Slice.Builder builder, T parent) {
+            mBuilder = builder;
+            mParent = parent;
+        }
+
+        /**
+         * @hide
+         */
+        @RestrictTo(RestrictTo.Scope.LIBRARY)
+        public SubTemplateSliceBuilder(Slice.Builder builder) {
+            mBuilder = builder;
+            mParent = null;
+        }
+
+        /**
+         * @hide
+         */
+        @RestrictTo(RestrictTo.Scope.LIBRARY)
+        public Slice.Builder getBuilder() {
+            return mBuilder;
+        }
+
+        /**
+         * Construct the slice.
+         */
+        public Slice build() {
+            return mBuilder.build();
+        }
+
+        /**
+         * Construct the slice and return to the parent object. If this object was not
+         * created from a {@link TemplateSliceBuilder} it will return null.
+         * @return parent builder
+         * @hide
+         */
+        @RestrictTo(RestrictTo.Scope.LIBRARY)
+        public T finish() {
+            if (mParent != null) {
+                mParent.add(this);
+            }
+            return mParent;
+        }
+    }
+}
diff --git a/slices/core/build.gradle b/slices/core/build.gradle
new file mode 100644
index 0000000..da89415
--- /dev/null
+++ b/slices/core/build.gradle
@@ -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.
+ */
+
+plugins {
+    id("SupportAndroidLibraryPlugin")
+}
+
+dependencies {
+    implementation project(":support-annotations")
+}
+
+android {
+    defaultConfig {
+        minSdkVersion 28
+    }
+}
+
+supportLibrary {
+    name 'Common utilities for slices'
+    publish false
+    inceptionYear '2017'
+    description "The slices core library provides utilities for the slices view and provider libraries"
+    java8Library true
+}
diff --git a/slices/core/lint-baseline.xml b/slices/core/lint-baseline.xml
new file mode 100644
index 0000000..2cadde1
--- /dev/null
+++ b/slices/core/lint-baseline.xml
@@ -0,0 +1,4 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<issues format="4" by="lint 3.0.0">
+
+</issues>
diff --git a/slices/core/src/main/AndroidManifest.xml b/slices/core/src/main/AndroidManifest.xml
new file mode 100644
index 0000000..ca86418
--- /dev/null
+++ b/slices/core/src/main/AndroidManifest.xml
@@ -0,0 +1,19 @@
+<?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.
+-->
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+          package="androidx.app.slice.core">
+    <uses-sdk android:minSdkVersion="28"/>
+</manifest>
diff --git a/slices/core/src/main/java/androidx/app/slice/core/SliceQuery.java b/slices/core/src/main/java/androidx/app/slice/core/SliceQuery.java
new file mode 100644
index 0000000..570c249
--- /dev/null
+++ b/slices/core/src/main/java/androidx/app/slice/core/SliceQuery.java
@@ -0,0 +1,233 @@
+/*
+ * 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 androidx.app.slice.core;
+
+import android.app.slice.Slice;
+import android.app.slice.SliceItem;
+import android.support.annotation.RestrictTo;
+import android.text.TextUtils;
+
+import java.util.Iterator;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.Queue;
+import java.util.Spliterators;
+import java.util.stream.Collectors;
+import java.util.stream.Stream;
+import java.util.stream.StreamSupport;
+
+/**
+ * Utilities for finding content within a Slice.
+ * @hide
+ */
+@RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
+public class SliceQuery {
+
+    /**
+     */
+    public static boolean hasAnyHints(SliceItem item, String... hints) {
+        if (hints == null) return false;
+        List<String> itemHints = item.getHints();
+        for (String hint : hints) {
+            if (itemHints.contains(hint)) {
+                return true;
+            }
+        }
+        return false;
+    }
+
+    /**
+     */
+    public static boolean hasHints(SliceItem item, String... hints) {
+        if (hints == null) return true;
+        List<String> itemHints = item.getHints();
+        for (String hint : hints) {
+            if (!TextUtils.isEmpty(hint) && !itemHints.contains(hint)) {
+                return false;
+            }
+        }
+        return true;
+    }
+
+    /**
+     */
+    public static boolean hasHints(Slice item, String... hints) {
+        if (hints == null) return true;
+        List<String> itemHints = item.getHints();
+        for (String hint : hints) {
+            if (!TextUtils.isEmpty(hint) && !itemHints.contains(hint)) {
+                return false;
+            }
+        }
+        return true;
+    }
+
+    /**
+     */
+    public static SliceItem getPrimaryIcon(Slice slice) {
+        for (SliceItem item : slice.getItems()) {
+            if (item.getType() == SliceItem.TYPE_IMAGE) {
+                return item;
+            }
+            if (!(item.getType() == SliceItem.TYPE_SLICE && item.hasHint(Slice.HINT_LIST))
+                    && !item.hasHint(Slice.HINT_ACTIONS)
+                    && !item.hasHint(Slice.HINT_LIST_ITEM)
+                    && (item.getType() != SliceItem.TYPE_ACTION)) {
+                SliceItem icon = SliceQuery.find(item, SliceItem.TYPE_IMAGE);
+                if (icon != null) {
+                    return icon;
+                }
+            }
+        }
+        return null;
+    }
+
+    /**
+     */
+    public static SliceItem findNotContaining(SliceItem container, List<SliceItem> list) {
+        SliceItem ret = null;
+        while (ret == null && list.size() != 0) {
+            SliceItem remove = list.remove(0);
+            if (!contains(container, remove)) {
+                ret = remove;
+            }
+        }
+        return ret;
+    }
+
+    /**
+     */
+    private static boolean contains(SliceItem container, SliceItem item) {
+        if (container == null || item == null) return false;
+        return stream(container).filter(s -> (s == item)).findAny().isPresent();
+    }
+
+    /**
+     */
+    public static List<SliceItem> findAll(SliceItem s, int type) {
+        return findAll(s, type, (String[]) null, null);
+    }
+
+    /**
+     */
+    public static List<SliceItem> findAll(Slice s, int type, String hints, String nonHints) {
+        return findAll(s, type, new String[]{ hints }, new String[]{ nonHints });
+    }
+
+    /**
+     */
+    public static List<SliceItem> findAll(SliceItem s, int type, String hints, String nonHints) {
+        return findAll(s, type, new String[]{ hints }, new String[]{ nonHints });
+    }
+
+    /**
+     */
+    public static List<SliceItem> findAll(Slice s, int type, String[] hints,
+            String[] nonHints) {
+        return stream(s).filter(item -> (type == -1 || item.getType() == type)
+                && (hasHints(item, hints) && !hasAnyHints(item, nonHints)))
+                .collect(Collectors.toList());
+    }
+
+    /**
+     */
+    public static List<SliceItem> findAll(SliceItem s, int type, String[] hints,
+            String[] nonHints) {
+        return stream(s).filter(item -> (type == -1 || item.getType() == type)
+                && (hasHints(item, hints) && !hasAnyHints(item, nonHints)))
+                .collect(Collectors.toList());
+    }
+
+    /**
+     */
+    public static SliceItem find(Slice s, int type, String hints, String nonHints) {
+        return find(s, type, new String[]{ hints }, new String[]{ nonHints });
+    }
+
+    /**
+     */
+    public static SliceItem find(Slice s, int type) {
+        return find(s, type, (String[]) null, null);
+    }
+
+    /**
+     */
+    public static SliceItem find(SliceItem s, int type) {
+        return find(s, type, (String[]) null, null);
+    }
+
+    /**
+     */
+    public static SliceItem find(SliceItem s, int type, String hints, String nonHints) {
+        return find(s, type, new String[]{ hints }, new String[]{ nonHints });
+    }
+
+    /**
+     */
+    public static SliceItem find(Slice s, int type, String[] hints, String[] nonHints) {
+        List<String> h = s.getHints();
+        return stream(s).filter(item -> (item.getType() == type || type == -1)
+                && (hasHints(item, hints) && !hasAnyHints(item, nonHints))).findFirst()
+                .orElse(null);
+    }
+
+    /**
+     */
+    public static SliceItem find(SliceItem s, int type, String[] hints, String[] nonHints) {
+        return stream(s).filter(item -> (item.getType() == type || type == -1)
+                && (hasHints(item, hints) && !hasAnyHints(item, nonHints))).findFirst()
+                .orElse(null);
+    }
+
+    /**
+     */
+    public static Stream<SliceItem> stream(SliceItem slice) {
+        Queue<SliceItem> items = new LinkedList();
+        items.add(slice);
+        return getSliceItemStream(items);
+    }
+
+    /**
+     */
+    public static Stream<SliceItem> stream(Slice slice) {
+        Queue<SliceItem> items = new LinkedList();
+        items.addAll(slice.getItems());
+        return getSliceItemStream(items);
+    }
+
+    /**
+     */
+    private static Stream<SliceItem> getSliceItemStream(Queue<SliceItem> items) {
+        Iterator<SliceItem> iterator = new Iterator<SliceItem>() {
+            @Override
+            public boolean hasNext() {
+                return items.size() != 0;
+            }
+
+            @Override
+            public SliceItem next() {
+                SliceItem item = items.poll();
+                if (item.getType() == SliceItem.TYPE_SLICE
+                        || item.getType() == SliceItem.TYPE_ACTION) {
+                    items.addAll(item.getSlice().getItems());
+                }
+                return item;
+            }
+        };
+        return StreamSupport.stream(Spliterators.spliteratorUnknownSize(iterator, 0), false);
+    }
+}
diff --git a/slices/view/build.gradle b/slices/view/build.gradle
new file mode 100644
index 0000000..a0f8196
--- /dev/null
+++ b/slices/view/build.gradle
@@ -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.
+ */
+
+plugins {
+    id("SupportAndroidLibraryPlugin")
+}
+
+dependencies {
+    implementation project(":slices-core")
+    implementation project(":recyclerview-v7")
+}
+
+android {
+    defaultConfig {
+        minSdkVersion 28
+    }
+}
+
+supportLibrary {
+    name 'Slice views'
+    publish false
+    inceptionYear '2017'
+    description "A library that handles rendering of slice content into supported templates"
+    java8Library true
+}
diff --git a/slices/view/lint-baseline.xml b/slices/view/lint-baseline.xml
new file mode 100644
index 0000000..49d372e
--- /dev/null
+++ b/slices/view/lint-baseline.xml
@@ -0,0 +1,15 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<issues format="4" by="lint 3.0.0">
+
+    <issue
+        id="WrongConstant"
+        message="Must be one of: SliceView.MODE_SMALL, SliceView.MODE_LARGE, SliceView.MODE_SHORTCUT"
+        errorLine1="        return mMode;"
+        errorLine2="               ~~~~~">
+        <location
+            file="src/main/java/androidx/app/slice/widget/SliceView.java"
+            line="290"
+            column="16"/>
+    </issue>
+
+</issues>
diff --git a/slices/view/src/main/AndroidManifest.xml b/slices/view/src/main/AndroidManifest.xml
new file mode 100644
index 0000000..5e9dd17
--- /dev/null
+++ b/slices/view/src/main/AndroidManifest.xml
@@ -0,0 +1,19 @@
+<?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.
+-->
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+          package="androidx.app.slice.view">
+    <uses-sdk android:minSdkVersion="28"/>
+</manifest>
diff --git a/slices/view/src/main/java/androidx/app/slice/widget/ActionRow.java b/slices/view/src/main/java/androidx/app/slice/widget/ActionRow.java
new file mode 100644
index 0000000..0a09620
--- /dev/null
+++ b/slices/view/src/main/java/androidx/app/slice/widget/ActionRow.java
@@ -0,0 +1,204 @@
+/*
+ * 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 androidx.app.slice.widget;
+
+import android.app.PendingIntent;
+import android.app.PendingIntent.CanceledException;
+import android.app.RemoteInput;
+import android.app.slice.Slice;
+import android.app.slice.SliceItem;
+import android.content.Context;
+import android.content.res.ColorStateList;
+import android.graphics.Color;
+import android.graphics.drawable.Icon;
+import android.os.AsyncTask;
+import android.support.annotation.RestrictTo;
+import android.util.TypedValue;
+import android.view.View;
+import android.view.ViewParent;
+import android.widget.FrameLayout;
+import android.widget.ImageView;
+import android.widget.ImageView.ScaleType;
+import android.widget.LinearLayout;
+import android.widget.TextView;
+
+import androidx.app.slice.core.SliceQuery;
+
+/**
+ * @hide
+ */
+@RestrictTo(RestrictTo.Scope.LIBRARY)
+public class ActionRow extends FrameLayout {
+
+    private static final int MAX_ACTIONS = 5;
+    private final int mSize;
+    private final int mIconPadding;
+    private final LinearLayout mActionsGroup;
+    private final boolean mFullActions;
+    private int mColor = Color.BLACK;
+
+    public ActionRow(Context context, boolean fullActions) {
+        super(context);
+        mFullActions = fullActions;
+        mSize = (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 48,
+                context.getResources().getDisplayMetrics());
+        mIconPadding = (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 12,
+                context.getResources().getDisplayMetrics());
+        mActionsGroup = new LinearLayout(context);
+        mActionsGroup.setOrientation(LinearLayout.HORIZONTAL);
+        mActionsGroup.setLayoutParams(
+                new LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.WRAP_CONTENT));
+        addView(mActionsGroup);
+    }
+
+    private void setColor(int color) {
+        mColor = color;
+        for (int i = 0; i < mActionsGroup.getChildCount(); i++) {
+            View view = mActionsGroup.getChildAt(i);
+            SliceItem item = (SliceItem) view.getTag();
+            boolean tint = !item.hasHint(Slice.HINT_NO_TINT);
+            if (tint) {
+                ((ImageView) view).setImageTintList(ColorStateList.valueOf(mColor));
+            }
+        }
+    }
+
+    private ImageView addAction(Icon icon, boolean allowTint, SliceItem image) {
+        ImageView imageView = new ImageView(getContext());
+        imageView.setPadding(mIconPadding, mIconPadding, mIconPadding, mIconPadding);
+        imageView.setScaleType(ScaleType.FIT_CENTER);
+        imageView.setImageIcon(icon);
+        if (allowTint) {
+            imageView.setImageTintList(ColorStateList.valueOf(mColor));
+        }
+        imageView.setBackground(SliceViewUtil.getDrawable(getContext(),
+                android.R.attr.selectableItemBackground));
+        imageView.setTag(image);
+        addAction(imageView);
+        return imageView;
+    }
+
+    /**
+     * Set the actions and color for this action row.
+     */
+    public void setActions(SliceItem actionRow, SliceItem defColor) {
+        removeAllViews();
+        mActionsGroup.removeAllViews();
+        addView(mActionsGroup);
+
+        SliceItem color = SliceQuery.find(actionRow, SliceItem.TYPE_COLOR);
+        if (color == null) {
+            color = defColor;
+        }
+        if (color != null) {
+            setColor(color.getColor());
+        }
+        SliceQuery.findAll(actionRow, SliceItem.TYPE_ACTION).forEach(action -> {
+            if (mActionsGroup.getChildCount() >= MAX_ACTIONS) {
+                return;
+            }
+            SliceItem image = SliceQuery.find(action, SliceItem.TYPE_IMAGE);
+            if (image == null) {
+                return;
+            }
+            boolean tint = !image.hasHint(Slice.HINT_NO_TINT);
+            SliceItem input = SliceQuery.find(action, SliceItem.TYPE_REMOTE_INPUT);
+            if (input != null && input.getRemoteInput().getAllowFreeFormInput()) {
+                addAction(image.getIcon(), tint, image).setOnClickListener(
+                        v -> handleRemoteInputClick(v, action.getAction(), input.getRemoteInput()));
+                createRemoteInputView(mColor, getContext());
+            } else {
+                addAction(image.getIcon(), tint, image).setOnClickListener(v -> AsyncTask.execute(
+                        () -> {
+                            try {
+                                action.getAction().send();
+                            } catch (CanceledException e) {
+                                e.printStackTrace();
+                            }
+                        }));
+            }
+        });
+        setVisibility(getChildCount() != 0 ? View.VISIBLE : View.GONE);
+    }
+
+    private void addAction(View child) {
+        mActionsGroup.addView(child, new LinearLayout.LayoutParams(mSize, mSize, 1));
+    }
+
+    private void createRemoteInputView(int color, Context context) {
+        View riv = RemoteInputView.inflate(context, this);
+        riv.setVisibility(View.INVISIBLE);
+        addView(riv, new LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT));
+        riv.setBackgroundColor(color);
+    }
+
+    private boolean handleRemoteInputClick(View view, PendingIntent pendingIntent,
+            RemoteInput input) {
+        if (input == null) {
+            return false;
+        }
+
+        ViewParent p = view.getParent().getParent();
+        RemoteInputView riv = null;
+        while (p != null) {
+            if (p instanceof View) {
+                View pv = (View) p;
+                riv = findRemoteInputView(pv);
+                if (riv != null) {
+                    break;
+                }
+            }
+            p = p.getParent();
+        }
+        if (riv == null) {
+            return false;
+        }
+
+        int width = view.getWidth();
+        if (view instanceof TextView) {
+            // Center the reveal on the text which might be off-center from the TextView
+            TextView tv = (TextView) view;
+            if (tv.getLayout() != null) {
+                int innerWidth = (int) tv.getLayout().getLineWidth(0);
+                innerWidth += tv.getCompoundPaddingLeft() + tv.getCompoundPaddingRight();
+                width = Math.min(width, innerWidth);
+            }
+        }
+        int cx = view.getLeft() + width / 2;
+        int cy = view.getTop() + view.getHeight() / 2;
+        int w = riv.getWidth();
+        int h = riv.getHeight();
+        int r = Math.max(
+                Math.max(cx + cy, cx + (h - cy)),
+                Math.max((w - cx) + cy, (w - cx) + (h - cy)));
+
+        riv.setRevealParameters(cx, cy, r);
+        riv.setPendingIntent(pendingIntent);
+        riv.setRemoteInput(new RemoteInput[] {
+                input
+        }, input);
+        riv.focusAnimated();
+        return true;
+    }
+
+    private RemoteInputView findRemoteInputView(View v) {
+        if (v == null) {
+            return null;
+        }
+        return (RemoteInputView) v.findViewWithTag(RemoteInputView.VIEW_TAG);
+    }
+}
diff --git a/slices/view/src/main/java/androidx/app/slice/widget/GridView.java b/slices/view/src/main/java/androidx/app/slice/widget/GridView.java
new file mode 100644
index 0000000..c320c72
--- /dev/null
+++ b/slices/view/src/main/java/androidx/app/slice/widget/GridView.java
@@ -0,0 +1,185 @@
+/*
+ * 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 androidx.app.slice.widget;
+
+import static android.view.ViewGroup.LayoutParams.MATCH_PARENT;
+import static android.view.ViewGroup.LayoutParams.WRAP_CONTENT;
+
+import android.app.slice.Slice;
+import android.app.slice.SliceItem;
+import android.content.Context;
+import android.graphics.Color;
+import android.support.annotation.RestrictTo;
+import android.util.AttributeSet;
+import android.util.TypedValue;
+import android.view.Gravity;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.widget.FrameLayout;
+import android.widget.ImageView;
+import android.widget.ImageView.ScaleType;
+import android.widget.LinearLayout;
+import android.widget.TextView;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import androidx.app.slice.core.SliceQuery;
+import androidx.app.slice.view.R;
+
+/**
+ * @hide
+ */
+@RestrictTo(RestrictTo.Scope.LIBRARY)
+public class GridView extends LinearLayout implements LargeSliceAdapter.SliceListView {
+
+    private static final String TAG = "GridView";
+
+    private static final int MAX_IMAGES = 3;
+    private static final int MAX_ALL = 5;
+    private boolean mIsAllImages;
+
+    public GridView(Context context, AttributeSet attrs) {
+        super(context, attrs);
+    }
+
+    @Override
+    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
+        if (mIsAllImages) {
+            int width = MeasureSpec.getSize(widthMeasureSpec);
+            int height = width / getChildCount();
+            heightMeasureSpec = MeasureSpec.makeMeasureSpec(height, MeasureSpec.EXACTLY);
+            getLayoutParams().height = height;
+            for (int i = 0; i < getChildCount(); i++) {
+                getChildAt(i).getLayoutParams().height = height;
+            }
+        }
+        super.onMeasure(widthMeasureSpec, heightMeasureSpec);
+    }
+
+    @Override
+    public void setSliceItem(SliceItem slice) {
+        mIsAllImages = true;
+        removeAllViews();
+        int total = 1;
+        if (slice.getType() == SliceItem.TYPE_SLICE) {
+            List<SliceItem> items = slice.getSlice().getItems();
+            total = items.size();
+            for (int i = 0; i < total; i++) {
+                SliceItem item = items.get(i);
+                if (isFull()) {
+                    continue;
+                }
+                if (!addItem(item)) {
+                    mIsAllImages = false;
+                }
+            }
+        } else {
+            if (!isFull()) {
+                if (!addItem(slice)) {
+                    mIsAllImages = false;
+                }
+            }
+        }
+        if (total > getChildCount() && mIsAllImages) {
+            addExtraCount(total - getChildCount());
+        }
+    }
+
+    private void addExtraCount(int numExtra) {
+        View last = getChildAt(getChildCount() - 1);
+        FrameLayout frame = new FrameLayout(getContext());
+        frame.setLayoutParams(last.getLayoutParams());
+
+        removeView(last);
+        frame.addView(last, new LayoutParams(MATCH_PARENT, MATCH_PARENT));
+
+        TextView v = new TextView(getContext());
+        v.setTextColor(Color.WHITE);
+        v.setBackgroundColor(0x4d000000);
+        v.setText(getResources().getString(R.string.abc_slice_more_content, numExtra));
+        v.setTextSize(TypedValue.COMPLEX_UNIT_DIP, 18);
+        v.setGravity(Gravity.CENTER);
+        frame.addView(v, new LayoutParams(MATCH_PARENT, MATCH_PARENT));
+
+        addView(frame);
+    }
+
+    private boolean isFull() {
+        return getChildCount() >= (mIsAllImages ? MAX_IMAGES : MAX_ALL);
+    }
+
+    /**
+     * Returns true if this item is just an image.
+     */
+    private boolean addItem(SliceItem item) {
+        if (item.getType() == SliceItem.TYPE_IMAGE) {
+            ImageView v = new ImageView(getContext());
+            v.setImageIcon(item.getIcon());
+            v.setScaleType(ScaleType.CENTER_CROP);
+            addView(v, new LayoutParams(0, MATCH_PARENT, 1));
+            return true;
+        } else {
+            LinearLayout v = new LinearLayout(getContext());
+            int s = (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP,
+                    12, getContext().getResources().getDisplayMetrics());
+            v.setPadding(0, s, 0, 0);
+            v.setOrientation(LinearLayout.VERTICAL);
+            v.setGravity(Gravity.CENTER_HORIZONTAL);
+            // TODO: Unify sporadic inflates that happen throughout the code.
+            ArrayList<SliceItem> items = new ArrayList<>();
+            if (item.getType() == SliceItem.TYPE_SLICE) {
+                items.addAll(item.getSlice().getItems());
+            }
+            items.forEach(i -> {
+                Context context = getContext();
+                switch (i.getType()) {
+                    case SliceItem.TYPE_TEXT:
+                        boolean title = false;
+                        if ((SliceQuery.hasAnyHints(item, new String[] {
+                                Slice.HINT_LARGE, Slice.HINT_TITLE
+                        }))) {
+                            title = true;
+                        }
+                        TextView tv = (TextView) LayoutInflater.from(context).inflate(title
+                                ? R.layout.abc_slice_title : R.layout.abc_slice_secondary_text,
+                                null);
+                        tv.setText(i.getText());
+                        v.addView(tv);
+                        break;
+                    case SliceItem.TYPE_IMAGE:
+                        ImageView iv = new ImageView(context);
+                        iv.setImageIcon(i.getIcon());
+                        if (item.hasHint(Slice.HINT_LARGE)) {
+                            iv.setLayoutParams(new LayoutParams(WRAP_CONTENT, WRAP_CONTENT));
+                        } else {
+                            int size = (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP,
+                                    48, context.getResources().getDisplayMetrics());
+                            iv.setLayoutParams(new LayoutParams(size, size));
+                        }
+                        v.addView(iv);
+                        break;
+                    case SliceItem.TYPE_COLOR:
+                        // TODO: Support color to tint stuff here.
+                        break;
+                }
+            });
+            addView(v, new LayoutParams(0, WRAP_CONTENT, 1));
+            return false;
+        }
+    }
+}
diff --git a/slices/view/src/main/java/androidx/app/slice/widget/LargeSliceAdapter.java b/slices/view/src/main/java/androidx/app/slice/widget/LargeSliceAdapter.java
new file mode 100644
index 0000000..ff513c5
--- /dev/null
+++ b/slices/view/src/main/java/androidx/app/slice/widget/LargeSliceAdapter.java
@@ -0,0 +1,211 @@
+/*
+ * 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 androidx.app.slice.widget;
+
+import android.app.slice.Slice;
+import android.app.slice.SliceItem;
+import android.content.Context;
+import android.support.annotation.RestrictTo;
+import android.support.v7.widget.RecyclerView;
+import android.util.ArrayMap;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+import android.view.ViewGroup.LayoutParams;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.stream.Collectors;
+
+import androidx.app.slice.core.SliceQuery;
+import androidx.app.slice.view.R;
+
+/**
+ * @hide
+ */
+@RestrictTo(RestrictTo.Scope.LIBRARY)
+public class LargeSliceAdapter extends RecyclerView.Adapter<LargeSliceAdapter.SliceViewHolder> {
+
+    public static final int TYPE_DEFAULT       = 1;
+    public static final int TYPE_HEADER        = 2;
+    public static final int TYPE_GRID          = 3;
+    public static final int TYPE_MESSAGE       = 4;
+    public static final int TYPE_MESSAGE_LOCAL = 5;
+
+    private final IdGenerator mIdGen = new IdGenerator();
+    private final Context mContext;
+    private List<SliceWrapper> mSlices = new ArrayList<>();
+    private SliceItem mColor;
+
+    public LargeSliceAdapter(Context context) {
+        mContext = context;
+        setHasStableIds(true);
+    }
+
+    /**
+     * Set the {@link SliceItem}'s to be displayed in the adapter and the accent color.
+     */
+    public void setSliceItems(List<SliceItem> slices, SliceItem color) {
+        mColor = color;
+        mIdGen.resetUsage();
+        mSlices = slices.stream().map(s -> new SliceWrapper(s, mIdGen))
+                .collect(Collectors.toList());
+        notifyDataSetChanged();
+    }
+
+    @Override
+    public SliceViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
+        View v = inflateForType(viewType);
+        v.setLayoutParams(new LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.WRAP_CONTENT));
+        return new SliceViewHolder(v);
+    }
+
+    @Override
+    public int getItemViewType(int position) {
+        return mSlices.get(position).mType;
+    }
+
+    @Override
+    public long getItemId(int position) {
+        return mSlices.get(position).mId;
+    }
+
+    @Override
+    public int getItemCount() {
+        return mSlices.size();
+    }
+
+    @Override
+    public void onBindViewHolder(SliceViewHolder holder, int position) {
+        SliceWrapper slice = mSlices.get(position);
+        if (holder.mSliceView != null) {
+            holder.mSliceView.setColor(mColor);
+            holder.mSliceView.setSliceItem(slice.mItem);
+        }
+    }
+
+    private View inflateForType(int viewType) {
+        switch (viewType) {
+            case TYPE_GRID:
+                return LayoutInflater.from(mContext).inflate(R.layout.abc_slice_grid, null);
+            case TYPE_MESSAGE:
+                return LayoutInflater.from(mContext).inflate(R.layout.abc_slice_message, null);
+            case TYPE_MESSAGE_LOCAL:
+                return LayoutInflater.from(mContext).inflate(R.layout.abc_slice_message_local,
+                        null);
+        }
+        return new SmallTemplateView(mContext);
+    }
+
+    protected static class SliceWrapper {
+        private final SliceItem mItem;
+        private final int mType;
+        private final long mId;
+
+        public SliceWrapper(SliceItem item, IdGenerator idGen) {
+            mItem = item;
+            mType = getType(item);
+            mId = idGen.getId(item);
+        }
+
+        public static int getType(SliceItem item) {
+            if (item.hasHint(Slice.HINT_MESSAGE)) {
+                // TODO: Better way to determine me or not? Something more like Messaging style.
+                if (SliceQuery.find(item, -1, Slice.HINT_SOURCE, null) != null) {
+                    return TYPE_MESSAGE;
+                } else {
+                    return TYPE_MESSAGE_LOCAL;
+                }
+            }
+            if (item.hasHint(Slice.HINT_HORIZONTAL)) {
+                return TYPE_GRID;
+            }
+            return TYPE_DEFAULT;
+        }
+    }
+
+    /**
+     * A {@link RecyclerView.ViewHolder} for presenting slices in {@link LargeSliceAdapter}.
+     */
+    public static class SliceViewHolder extends RecyclerView.ViewHolder {
+        public final SliceListView mSliceView;
+
+        public SliceViewHolder(View itemView) {
+            super(itemView);
+            mSliceView = itemView instanceof SliceListView ? (SliceListView) itemView : null;
+        }
+    }
+
+    /**
+     * View slices being displayed in {@link LargeSliceAdapter}.
+     */
+    public interface SliceListView {
+        /**
+         * Set the slice item for this view.
+         */
+        void setSliceItem(SliceItem slice);
+
+        /**
+         * Set the color for the items in this view.
+         */
+        default void setColor(SliceItem color) {
+
+        }
+    }
+
+    private static class IdGenerator {
+        private long mNextLong = 0;
+        private final ArrayMap<String, Long> mCurrentIds = new ArrayMap<>();
+        private final ArrayMap<String, Integer> mUsedIds = new ArrayMap<>();
+
+        public long getId(SliceItem item) {
+            String str = genString(item);
+            if (!mCurrentIds.containsKey(str)) {
+                mCurrentIds.put(str, mNextLong++);
+            }
+            long id = mCurrentIds.get(str);
+            int index = mUsedIds.getOrDefault(str, 0);
+            mUsedIds.put(str, index + 1);
+            return id + index * 10000;
+        }
+
+        private String genString(SliceItem item) {
+            StringBuilder builder = new StringBuilder();
+            SliceQuery.stream(item).forEach(i -> {
+                builder.append(i.getType());
+                //i.removeHint(Slice.HINT_SELECTED);
+                builder.append(i.getHints());
+                switch (i.getType()) {
+                    case SliceItem.TYPE_IMAGE:
+                        builder.append(i.getIcon());
+                        break;
+                    case SliceItem.TYPE_TEXT:
+                        builder.append(i.getText());
+                        break;
+                    case SliceItem.TYPE_COLOR:
+                        builder.append(i.getColor());
+                        break;
+                }
+            });
+            return builder.toString();
+        }
+
+        public void resetUsage() {
+            mUsedIds.clear();
+        }
+    }
+}
diff --git a/slices/view/src/main/java/androidx/app/slice/widget/LargeTemplateView.java b/slices/view/src/main/java/androidx/app/slice/widget/LargeTemplateView.java
new file mode 100644
index 0000000..c5e82aa
--- /dev/null
+++ b/slices/view/src/main/java/androidx/app/slice/widget/LargeTemplateView.java
@@ -0,0 +1,121 @@
+/*
+ * 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 androidx.app.slice.widget;
+
+import static android.view.ViewGroup.LayoutParams.WRAP_CONTENT;
+
+import android.app.slice.Slice;
+import android.app.slice.SliceItem;
+import android.content.Context;
+import android.support.annotation.RestrictTo;
+import android.support.v7.widget.LinearLayoutManager;
+import android.support.v7.widget.RecyclerView;
+import android.util.TypedValue;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import androidx.app.slice.core.SliceQuery;
+
+/**
+ * @hide
+ */
+@RestrictTo(RestrictTo.Scope.LIBRARY)
+public class LargeTemplateView extends SliceView.SliceModeView {
+
+    private final LargeSliceAdapter mAdapter;
+    private final RecyclerView mRecyclerView;
+    private final int mDefaultHeight;
+    private final int mMaxHeight;
+    private Slice mSlice;
+    private boolean mIsScrollable;
+
+    public LargeTemplateView(Context context) {
+        super(context);
+
+        mRecyclerView = new RecyclerView(getContext());
+        mRecyclerView.setLayoutManager(new LinearLayoutManager(getContext()));
+        mAdapter = new LargeSliceAdapter(context);
+        mRecyclerView.setAdapter(mAdapter);
+        addView(mRecyclerView);
+        mDefaultHeight = (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 200,
+                getResources().getDisplayMetrics());
+        mMaxHeight = (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 200,
+                getResources().getDisplayMetrics());
+    }
+
+    @Override
+    public @SliceView.SliceMode int getMode() {
+        return SliceView.MODE_LARGE;
+    }
+
+    @Override
+    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
+        mRecyclerView.getLayoutParams().height = WRAP_CONTENT;
+        super.onMeasure(widthMeasureSpec, heightMeasureSpec);
+        if (mRecyclerView.getMeasuredHeight() > mMaxHeight
+                || (mSlice != null && SliceQuery.hasHints(mSlice, Slice.HINT_PARTIAL))) {
+            mRecyclerView.getLayoutParams().height = mDefaultHeight;
+        } else {
+            mRecyclerView.getLayoutParams().height = mRecyclerView.getMeasuredHeight();
+        }
+        super.onMeasure(widthMeasureSpec, heightMeasureSpec);
+    }
+
+    @Override
+    public void setSlice(Slice slice) {
+        SliceItem color = SliceQuery.find(slice, SliceItem.TYPE_COLOR);
+        mSlice = slice;
+        List<SliceItem> items = new ArrayList<>();
+        boolean[] hasHeader = new boolean[1];
+        if (SliceQuery.hasHints(slice, Slice.HINT_LIST)) {
+            addList(slice, items);
+        } else {
+            slice.getItems().forEach(item -> {
+                if (item.hasHint(Slice.HINT_ACTIONS)) {
+                    return;
+                } else if (item.getType() == SliceItem.TYPE_COLOR) {
+                    return;
+                } else if (item.getType() == SliceItem.TYPE_SLICE
+                        && item.hasHint(Slice.HINT_LIST)) {
+                    addList(item.getSlice(), items);
+                } else if (item.hasHint(Slice.HINT_LIST_ITEM)) {
+                    items.add(item);
+                } else if (!hasHeader[0]) {
+                    hasHeader[0] = true;
+                    items.add(0, item);
+                } else {
+                    items.add(item);
+                }
+            });
+        }
+        mAdapter.setSliceItems(items, color);
+    }
+
+    private void addList(Slice slice, List<SliceItem> items) {
+        List<SliceItem> sliceItems = slice.getItems();
+        items.addAll(sliceItems);
+    }
+
+    /**
+     * Whether or not the content in this template should be scrollable.
+     */
+    public void setScrollable(boolean isScrollable) {
+        // TODO -- restrict / enable how much this view can show
+        mIsScrollable = isScrollable;
+    }
+}
diff --git a/slices/view/src/main/java/androidx/app/slice/widget/MessageView.java b/slices/view/src/main/java/androidx/app/slice/widget/MessageView.java
new file mode 100644
index 0000000..9db35bb
--- /dev/null
+++ b/slices/view/src/main/java/androidx/app/slice/widget/MessageView.java
@@ -0,0 +1,79 @@
+/*
+ * 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 androidx.app.slice.widget;
+
+import android.app.slice.Slice;
+import android.app.slice.SliceItem;
+import android.content.Context;
+import android.graphics.Bitmap;
+import android.graphics.Canvas;
+import android.graphics.drawable.Drawable;
+import android.support.annotation.RestrictTo;
+import android.text.SpannableStringBuilder;
+import android.util.AttributeSet;
+import android.util.TypedValue;
+import android.widget.ImageView;
+import android.widget.LinearLayout;
+import android.widget.TextView;
+
+import androidx.app.slice.core.SliceQuery;
+
+/**
+ * @hide
+ */
+@RestrictTo(RestrictTo.Scope.LIBRARY)
+public class MessageView extends LinearLayout implements LargeSliceAdapter.SliceListView {
+
+    private TextView mDetails;
+    private ImageView mIcon;
+
+    public MessageView(Context context, AttributeSet attrs) {
+        super(context, attrs);
+    }
+
+    @Override
+    protected void onFinishInflate() {
+        super.onFinishInflate();
+        mDetails = findViewById(android.R.id.summary);
+        mIcon = findViewById(android.R.id.icon);
+    }
+
+    @Override
+    public void setSliceItem(SliceItem slice) {
+        SliceItem source = SliceQuery.find(slice, SliceItem.TYPE_IMAGE, Slice.HINT_SOURCE, null);
+        if (source != null) {
+            final int iconSize = (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP,
+                    24, getContext().getResources().getDisplayMetrics());
+            // TODO try and turn this into a drawable
+            Bitmap iconBm = Bitmap.createBitmap(iconSize, iconSize, Bitmap.Config.ARGB_8888);
+            Canvas iconCanvas = new Canvas(iconBm);
+            Drawable d = source.getIcon().loadDrawable(getContext());
+            d.setBounds(0, 0, iconSize, iconSize);
+            d.draw(iconCanvas);
+            mIcon.setImageBitmap(SliceViewUtil.getCircularBitmap(iconBm));
+        }
+        SpannableStringBuilder builder = new SpannableStringBuilder();
+        SliceQuery.findAll(slice, SliceItem.TYPE_TEXT).forEach(text -> {
+            if (builder.length() != 0) {
+                builder.append('\n');
+            }
+            builder.append(text.getText());
+        });
+        mDetails.setText(builder.toString());
+    }
+
+}
diff --git a/slices/view/src/main/java/androidx/app/slice/widget/RemoteInputView.java b/slices/view/src/main/java/androidx/app/slice/widget/RemoteInputView.java
new file mode 100644
index 0000000..9d45501
--- /dev/null
+++ b/slices/view/src/main/java/androidx/app/slice/widget/RemoteInputView.java
@@ -0,0 +1,423 @@
+/*
+ * 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 androidx.app.slice.widget;
+
+import android.animation.Animator;
+import android.app.PendingIntent;
+import android.app.RemoteInput;
+import android.content.Context;
+import android.content.Intent;
+import android.graphics.Rect;
+import android.graphics.drawable.Drawable;
+import android.os.Bundle;
+import android.support.annotation.RestrictTo;
+import android.text.Editable;
+import android.text.TextWatcher;
+import android.util.AttributeSet;
+import android.util.Log;
+import android.view.KeyEvent;
+import android.view.LayoutInflater;
+import android.view.MotionEvent;
+import android.view.View;
+import android.view.ViewAnimationUtils;
+import android.view.ViewGroup;
+import android.view.accessibility.AccessibilityEvent;
+import android.view.inputmethod.CompletionInfo;
+import android.view.inputmethod.EditorInfo;
+import android.view.inputmethod.InputConnection;
+import android.view.inputmethod.InputMethodManager;
+import android.widget.EditText;
+import android.widget.ImageButton;
+import android.widget.LinearLayout;
+import android.widget.ProgressBar;
+import android.widget.TextView;
+import android.widget.Toast;
+
+import androidx.app.slice.view.R;
+
+/**
+ * Host for the remote input.
+ *
+ * @hide
+ */
+// TODO this should be unified with SystemUI RemoteInputView (b/67527720)
+@RestrictTo(RestrictTo.Scope.LIBRARY)
+public class RemoteInputView extends LinearLayout implements View.OnClickListener, TextWatcher {
+
+    private static final String TAG = "RemoteInput";
+
+    /**
+     * A marker object that let's us easily find views of this class.
+     */
+    public static final Object VIEW_TAG = new Object();
+
+    private RemoteEditText mEditText;
+    private ImageButton mSendButton;
+    private ProgressBar mProgressBar;
+    private PendingIntent mPendingIntent;
+    private RemoteInput[] mRemoteInputs;
+    private RemoteInput mRemoteInput;
+
+    private int mRevealCx;
+    private int mRevealCy;
+    private int mRevealR;
+    private boolean mResetting;
+
+    public RemoteInputView(Context context, AttributeSet attrs) {
+        super(context, attrs);
+    }
+
+    @Override
+    protected void onFinishInflate() {
+        super.onFinishInflate();
+
+        mProgressBar = findViewById(R.id.remote_input_progress);
+        mSendButton = findViewById(R.id.remote_input_send);
+        mSendButton.setOnClickListener(this);
+
+        mEditText = (RemoteEditText) getChildAt(0);
+        mEditText.setOnEditorActionListener(new TextView.OnEditorActionListener() {
+            @Override
+            public boolean onEditorAction(TextView v, int actionId, KeyEvent event) {
+                final boolean isSoftImeEvent = event == null
+                        && (actionId == EditorInfo.IME_ACTION_DONE
+                                || actionId == EditorInfo.IME_ACTION_NEXT
+                                || actionId == EditorInfo.IME_ACTION_SEND);
+                final boolean isKeyboardEnterKey = event != null
+                        && isConfirmKey(event.getKeyCode())
+                        && event.getAction() == KeyEvent.ACTION_DOWN;
+
+                if (isSoftImeEvent || isKeyboardEnterKey) {
+                    if (mEditText.length() > 0) {
+                        sendRemoteInput();
+                    }
+                    // Consume action to prevent IME from closing.
+                    return true;
+                }
+                return false;
+            }
+        });
+        mEditText.addTextChangedListener(this);
+        mEditText.setInnerFocusable(false);
+        mEditText.mRemoteInputView = this;
+    }
+
+    private void sendRemoteInput() {
+        Bundle results = new Bundle();
+        results.putString(mRemoteInput.getResultKey(), mEditText.getText().toString());
+        Intent fillInIntent = new Intent().addFlags(Intent.FLAG_RECEIVER_FOREGROUND);
+        RemoteInput.addResultsToIntent(mRemoteInputs, fillInIntent,
+                results);
+
+        mEditText.setEnabled(false);
+        mSendButton.setVisibility(INVISIBLE);
+        mProgressBar.setVisibility(VISIBLE);
+        mEditText.mShowImeOnInputConnection = false;
+
+        // TODO: Figure out API for telling the system about slice interaction.
+        // Tell ShortcutManager that this package has been "activated".  ShortcutManager
+        // will reset the throttling for this package.
+        // Strictly speaking, the intent receiver may be different from the intent creator,
+        // but that's an edge case, and also because we can't always know which package will receive
+        // an intent, so we just reset for the creator.
+        //getContext().getSystemService(ShortcutManager.class).onApplicationActive(
+        //        mPendingIntent.getCreatorPackage(),
+        //        getContext().getUserId());
+
+        try {
+            mPendingIntent.send(getContext(), 0, fillInIntent);
+            reset();
+        } catch (PendingIntent.CanceledException e) {
+            Log.i(TAG, "Unable to send remote input result", e);
+            Toast.makeText(getContext(), "Failure sending pending intent for inline reply :(",
+                    Toast.LENGTH_SHORT).show();
+            reset();
+        }
+    }
+
+    /**
+     * Creates a remote input view.
+     */
+    public static RemoteInputView inflate(Context context, ViewGroup root) {
+        RemoteInputView v = (RemoteInputView) LayoutInflater.from(context).inflate(
+                R.layout.abc_slice_remote_input, root, false);
+        v.setTag(VIEW_TAG);
+        return v;
+    }
+
+    @Override
+    public void onClick(View v) {
+        if (v == mSendButton) {
+            sendRemoteInput();
+        }
+    }
+
+    @Override
+    public boolean onTouchEvent(MotionEvent event) {
+        super.onTouchEvent(event);
+
+        // We never want for a touch to escape to an outer view or one we covered.
+        return true;
+    }
+
+    private void onDefocus() {
+        setVisibility(INVISIBLE);
+    }
+
+    /**
+     * Set the pending intent for remote input.
+     */
+    public void setPendingIntent(PendingIntent pendingIntent) {
+        mPendingIntent = pendingIntent;
+    }
+
+    /**
+     * Set the remote inputs for this view.
+     */
+    public void setRemoteInput(RemoteInput[] remoteInputs, RemoteInput remoteInput) {
+        mRemoteInputs = remoteInputs;
+        mRemoteInput = remoteInput;
+        mEditText.setHint(mRemoteInput.getLabel());
+    }
+
+    /**
+     * Focuses the remote input view.
+     */
+    public void focusAnimated() {
+        if (getVisibility() != VISIBLE) {
+            Animator animator = ViewAnimationUtils.createCircularReveal(
+                    this, mRevealCx, mRevealCy, 0, mRevealR);
+            animator.setDuration(200);
+            animator.start();
+        }
+        focus();
+    }
+
+    private void focus() {
+        setVisibility(VISIBLE);
+        mEditText.setInnerFocusable(true);
+        mEditText.mShowImeOnInputConnection = true;
+        mEditText.setSelection(mEditText.getText().length());
+        mEditText.requestFocus();
+        updateSendButton();
+    }
+
+    private void reset() {
+        mResetting = true;
+
+        mEditText.getText().clear();
+        mEditText.setEnabled(true);
+        mSendButton.setVisibility(VISIBLE);
+        mProgressBar.setVisibility(INVISIBLE);
+        updateSendButton();
+        onDefocus();
+
+        mResetting = false;
+    }
+
+    @Override
+    public boolean onRequestSendAccessibilityEvent(View child, AccessibilityEvent event) {
+        if (mResetting && child == mEditText) {
+            // Suppress text events if it happens during resetting. Ideally this would be
+            // suppressed by the text view not being shown, but that doesn't work here because it
+            // needs to stay visible for the animation.
+            return false;
+        }
+        return super.onRequestSendAccessibilityEvent(child, event);
+    }
+
+    private void updateSendButton() {
+        mSendButton.setEnabled(mEditText.getText().length() != 0);
+    }
+
+    @Override
+    public void beforeTextChanged(CharSequence s, int start, int count, int after) {
+    }
+
+    @Override
+    public void onTextChanged(CharSequence s, int start, int before, int count) {
+    }
+
+    @Override
+    public void afterTextChanged(Editable s) {
+        updateSendButton();
+    }
+
+    /**
+     * @hide
+     */
+    @RestrictTo(RestrictTo.Scope.LIBRARY)
+    public void setRevealParameters(int cx, int cy, int r) {
+        mRevealCx = cx;
+        mRevealCy = cy;
+        mRevealR = r;
+    }
+
+    @Override
+    public void dispatchStartTemporaryDetach() {
+        super.dispatchStartTemporaryDetach();
+        // Detach the EditText temporarily such that it doesn't get onDetachedFromWindow and
+        // won't lose IME focus.
+        detachViewFromParent(mEditText);
+    }
+
+    @Override
+    public void dispatchFinishTemporaryDetach() {
+        if (isAttachedToWindow()) {
+            attachViewToParent(mEditText, 0, mEditText.getLayoutParams());
+        } else {
+            removeDetachedView(mEditText, false /* animate */);
+        }
+        super.dispatchFinishTemporaryDetach();
+    }
+
+    /**
+     * An EditText that changes appearance based on whether it's focusable and becomes un-focusable
+     * whenever the user navigates away from it or it becomes invisible.
+     */
+    public static class RemoteEditText extends EditText {
+
+        private final Drawable mBackground;
+        private RemoteInputView mRemoteInputView;
+        boolean mShowImeOnInputConnection;
+
+        public RemoteEditText(Context context, AttributeSet attrs) {
+            super(context, attrs);
+            mBackground = getBackground();
+        }
+
+        private void defocusIfNeeded(boolean animate) {
+            if (mRemoteInputView != null || isTemporarilyDetached()) {
+                if (isTemporarilyDetached()) {
+                    // We might get reattached but then the other one of HUN / expanded might steal
+                    // our focus, so we'll need to save our text here.
+                }
+                return;
+            }
+            if (isFocusable() && isEnabled()) {
+                setInnerFocusable(false);
+                if (mRemoteInputView != null) {
+                    mRemoteInputView.onDefocus();
+                }
+                mShowImeOnInputConnection = false;
+            }
+        }
+
+        @Override
+        protected void onVisibilityChanged(View changedView, int visibility) {
+            super.onVisibilityChanged(changedView, visibility);
+
+            if (!isShown()) {
+                defocusIfNeeded(false /* animate */);
+            }
+        }
+
+        @Override
+        protected void onFocusChanged(boolean focused, int direction, Rect previouslyFocusedRect) {
+            super.onFocusChanged(focused, direction, previouslyFocusedRect);
+            if (!focused) {
+                defocusIfNeeded(true /* animate */);
+            }
+        }
+
+        @Override
+        public void getFocusedRect(Rect r) {
+            super.getFocusedRect(r);
+            r.top = getScrollY();
+            r.bottom = getScrollY() + (getBottom() - getTop());
+        }
+
+        @Override
+        public boolean onKeyDown(int keyCode, KeyEvent event) {
+            if (keyCode == KeyEvent.KEYCODE_BACK) {
+                // Eat the DOWN event here to prevent any default behavior.
+                return true;
+            }
+            return super.onKeyDown(keyCode, event);
+        }
+
+        @Override
+        public boolean onKeyUp(int keyCode, KeyEvent event) {
+            if (keyCode == KeyEvent.KEYCODE_BACK) {
+                defocusIfNeeded(true /* animate */);
+                return true;
+            }
+            return super.onKeyUp(keyCode, event);
+        }
+
+        @Override
+        public InputConnection onCreateInputConnection(EditorInfo outAttrs) {
+            final InputConnection inputConnection = super.onCreateInputConnection(outAttrs);
+
+            if (mShowImeOnInputConnection && inputConnection != null) {
+                final InputMethodManager imm = getContext().getSystemService(
+                        InputMethodManager.class);
+                if (imm != null) {
+                    // onCreateInputConnection is called by InputMethodManager in the middle of
+                    // setting up the connection to the IME; wait with requesting the IME until that
+                    // work has completed.
+                    post(new Runnable() {
+                        @Override
+                        public void run() {
+                            imm.viewClicked(RemoteEditText.this);
+                            imm.showSoftInput(RemoteEditText.this, 0);
+                        }
+                    });
+                }
+            }
+
+            return inputConnection;
+        }
+
+        @Override
+        public void onCommitCompletion(CompletionInfo text) {
+            clearComposingText();
+            setText(text.getText());
+            setSelection(getText().length());
+        }
+
+        void setInnerFocusable(boolean focusable) {
+            setFocusableInTouchMode(focusable);
+            setFocusable(focusable);
+            setCursorVisible(focusable);
+
+            if (focusable) {
+                requestFocus();
+                setBackground(mBackground);
+            } else {
+                setBackground(null);
+            }
+
+        }
+    }
+
+    /** Whether key will, by default, trigger a click on the focused view.
+     * @hide
+     */
+    @RestrictTo(RestrictTo.Scope.LIBRARY)
+    public static final boolean isConfirmKey(int keyCode) {
+        switch (keyCode) {
+            case KeyEvent.KEYCODE_DPAD_CENTER:
+            case KeyEvent.KEYCODE_ENTER:
+            case KeyEvent.KEYCODE_SPACE:
+            case KeyEvent.KEYCODE_NUMPAD_ENTER:
+                return true;
+            default:
+                return false;
+        }
+    }
+}
diff --git a/slices/view/src/main/java/androidx/app/slice/widget/ShortcutView.java b/slices/view/src/main/java/androidx/app/slice/widget/ShortcutView.java
new file mode 100644
index 0000000..b6e77cd
--- /dev/null
+++ b/slices/view/src/main/java/androidx/app/slice/widget/ShortcutView.java
@@ -0,0 +1,174 @@
+/*
+ * 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 androidx.app.slice.widget;
+
+import android.app.PendingIntent;
+import android.app.PendingIntent.CanceledException;
+import android.app.slice.Slice;
+import android.app.slice.SliceItem;
+import android.content.Context;
+import android.content.Intent;
+import android.content.pm.ApplicationInfo;
+import android.content.pm.PackageManager;
+import android.content.pm.ProviderInfo;
+import android.content.res.Resources;
+import android.graphics.Color;
+import android.graphics.drawable.Drawable;
+import android.graphics.drawable.ShapeDrawable;
+import android.graphics.drawable.shapes.OvalShape;
+import android.net.Uri;
+import android.support.annotation.RestrictTo;
+
+import androidx.app.slice.core.SliceQuery;
+import androidx.app.slice.view.R;
+
+/**
+ * @hide
+ */
+@RestrictTo(RestrictTo.Scope.LIBRARY)
+public class ShortcutView extends androidx.app.slice.widget.SliceView.SliceModeView {
+
+    private static final String TAG = "ShortcutView";
+
+    private Uri mUri;
+    private PendingIntent mAction;
+    private SliceItem mLabel;
+    private SliceItem mIcon;
+
+    private int mLargeIconSize;
+    private int mSmallIconSize;
+
+    public ShortcutView(Context context) {
+        super(context);
+        final Resources res = getResources();
+        mSmallIconSize = res.getDimensionPixelSize(R.dimen.abc_slice_icon_size);
+        mLargeIconSize = res.getDimensionPixelSize(R.dimen.abc_slice_shortcut_size);
+    }
+
+    @Override
+    public void setSlice(Slice slice) {
+        removeAllViews();
+        determineShortcutItems(getContext(), slice);
+        SliceItem colorItem = SliceQuery.find(slice, SliceItem.TYPE_COLOR);
+        if (colorItem == null) {
+            colorItem = SliceQuery.find(slice, SliceItem.TYPE_COLOR);
+        }
+        // TODO: pick better default colour
+        final int color = colorItem != null ? colorItem.getColor() : Color.GRAY;
+        ShapeDrawable circle = new ShapeDrawable(new OvalShape());
+        circle.setTint(color);
+        setBackground(circle);
+        if (mIcon != null) {
+            final boolean isLarge = mIcon.hasHint(Slice.HINT_LARGE)
+                    || mIcon.hasHint(Slice.HINT_SOURCE);
+            final int iconSize = isLarge ? mLargeIconSize : mSmallIconSize;
+            SliceViewUtil.createCircledIcon(getContext(), color, iconSize, mIcon.getIcon(),
+                    isLarge, this /* parent */);
+            mUri = slice.getUri();
+            setClickable(true);
+        } else {
+            setClickable(false);
+        }
+    }
+
+    @Override
+    public @SliceView.SliceMode int getMode() {
+        return SliceView.MODE_SHORTCUT;
+    }
+
+    @Override
+    public boolean performClick() {
+        if (!callOnClick()) {
+            try {
+                if (mAction != null) {
+                    mAction.send();
+                } else {
+                    Intent intent = new Intent(Intent.ACTION_VIEW).setData(mUri);
+                    intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
+                    getContext().startActivity(intent);
+                }
+            } catch (CanceledException e) {
+                e.printStackTrace();
+            }
+        }
+        return true;
+    }
+
+    /**
+     * Looks at the slice and determines which items are best to use to compose the shortcut.
+     */
+    private void determineShortcutItems(Context context, Slice slice) {
+        SliceItem titleItem = SliceQuery.find(slice, SliceItem.TYPE_ACTION,
+                Slice.HINT_TITLE, null);
+
+        if (titleItem != null) {
+            // Preferred case: hinted action containing hinted image and text
+            mAction = titleItem.getAction();
+            mIcon = SliceQuery.find(titleItem.getSlice(), SliceItem.TYPE_IMAGE, Slice.HINT_TITLE,
+                    null);
+            mLabel = SliceQuery.find(titleItem.getSlice(), SliceItem.TYPE_TEXT, Slice.HINT_TITLE,
+                    null);
+        } else {
+            // No hinted action; just use the first one
+            SliceItem actionItem = SliceQuery.find(slice, SliceItem.TYPE_ACTION, (String) null,
+                    null);
+            mAction = (actionItem != null) ? actionItem.getAction() : null;
+        }
+        // First fallback: any hinted image and text
+        if (mIcon == null) {
+            mIcon = SliceQuery.find(slice, SliceItem.TYPE_IMAGE, Slice.HINT_TITLE,
+                    null);
+        }
+        if (mLabel == null) {
+            mLabel = SliceQuery.find(slice, SliceItem.TYPE_TEXT, Slice.HINT_TITLE,
+                    null);
+        }
+        // Second fallback: first image and text
+        if (mIcon == null) {
+            mIcon = SliceQuery.find(slice, SliceItem.TYPE_IMAGE, (String) null,
+                    null);
+        }
+        if (mLabel == null) {
+            mLabel = SliceQuery.find(slice, SliceItem.TYPE_TEXT, (String) null,
+                    null);
+        }
+        // Final fallback: use app info
+        if (mIcon == null || mLabel == null || mAction == null) {
+            PackageManager pm = context.getPackageManager();
+            ProviderInfo providerInfo = pm.resolveContentProvider(
+                    slice.getUri().getAuthority(), 0);
+            ApplicationInfo appInfo = providerInfo.applicationInfo;
+            if (appInfo != null) {
+                if (mIcon == null) {
+                    Slice.Builder sb = new Slice.Builder(slice.getUri());
+                    Drawable icon = pm.getApplicationIcon(appInfo);
+                    sb.addIcon(SliceViewUtil.createIconFromDrawable(icon), Slice.HINT_LARGE);
+                    mIcon = sb.build().getItems().get(0);
+                }
+                if (mLabel == null) {
+                    Slice.Builder sb = new Slice.Builder(slice.getUri());
+                    sb.addText(pm.getApplicationLabel(appInfo));
+                    mLabel = sb.build().getItems().get(0);
+                }
+                if (mAction == null) {
+                    mAction = PendingIntent.getActivity(context, 0,
+                            pm.getLaunchIntentForPackage(appInfo.packageName), 0);
+                }
+            }
+        }
+    }
+}
diff --git a/slices/view/src/main/java/androidx/app/slice/widget/SliceView.java b/slices/view/src/main/java/androidx/app/slice/widget/SliceView.java
new file mode 100644
index 0000000..c35dc2c
--- /dev/null
+++ b/slices/view/src/main/java/androidx/app/slice/widget/SliceView.java
@@ -0,0 +1,421 @@
+/*
+ * 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 androidx.app.slice.widget;
+
+import android.app.slice.Slice;
+import android.app.slice.SliceItem;
+import android.content.ContentResolver;
+import android.content.Context;
+import android.content.Intent;
+import android.database.ContentObserver;
+import android.graphics.drawable.ColorDrawable;
+import android.net.Uri;
+import android.os.Handler;
+import android.os.Looper;
+import android.support.annotation.IntDef;
+import android.support.annotation.NonNull;
+import android.support.annotation.Nullable;
+import android.support.annotation.RestrictTo;
+import android.support.v4.util.Preconditions;
+import android.util.AttributeSet;
+import android.util.Log;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.FrameLayout;
+
+import java.util.List;
+
+import androidx.app.slice.core.SliceQuery;
+import androidx.app.slice.view.R;
+
+/**
+ * A view for displaying a {@link Slice} which is a piece of app content and actions. SliceView is
+ * able to present slice content in a templated format outside of the associated app. The way this
+ * content is displayed depends on the structure of the slice, the hints associated with the
+ * content, and the mode that SliceView is configured for. The modes that SliceView supports are:
+ * <ul>
+ * <li><b>Shortcut</b>: A shortcut is presented as an icon and a text label representing the main
+ * content or action associated with the slice.</li>
+ * <li><b>Small</b>: The small format has a restricted height and can present a single
+ * {@link SliceItem} or a limited collection of items.</li>
+ * <li><b>Large</b>: The large format displays multiple small templates in a list, if scrolling is
+ * not enabled (see {@link #setScrollable(boolean)}) the view will show as many items as it can
+ * comfortably fit.</li>
+ * </ul>
+ * <p>
+ * When constructing a slice, the contents of it can be annotated with hints, these provide the OS
+ * with some information on how the content should be displayed. For example, text annotated with
+ * {@link Slice#HINT_TITLE} would be placed in the title position of a template. A slice annotated
+ * with {@link Slice#HINT_LIST} would present the child items of that slice in a list.
+ * <p>
+ * SliceView can be provided a slice via a uri {@link #setSlice(Uri)} in which case a content
+ * observer will be set for that uri and the view will update if there are any changes to the slice.
+ * To use this the app must have a special permission to bind to the slice (see
+ * {@link android.Manifest.permission#BIND_SLICE}).
+ * <p>
+ * Example usage:
+ *
+ * <pre class="prettyprint">
+ * SliceView v = new SliceView(getContext());
+ * v.setMode(desiredMode);
+ * v.setSlice(sliceUri);
+ * </pre>
+ */
+public class SliceView extends ViewGroup {
+
+    private static final String TAG = "SliceView";
+
+    /**
+     * @hide
+     */
+    @RestrictTo(RestrictTo.Scope.LIBRARY)
+    public abstract static class SliceModeView extends FrameLayout {
+
+        public SliceModeView(Context context) {
+            super(context);
+        }
+
+        /**
+         * @return the mode of the slice being presented.
+         */
+        public abstract int getMode();
+
+        /**
+         * @param slice the slice to show in this view.
+         */
+        public abstract void setSlice(Slice slice);
+    }
+
+    /**
+     * @hide
+     */
+    @RestrictTo(RestrictTo.Scope.LIBRARY)
+    @IntDef({
+            MODE_SMALL, MODE_LARGE, MODE_SHORTCUT
+    })
+    public @interface SliceMode {}
+
+    /**
+     * Mode indicating this slice should be presented in small template format.
+     */
+    public static final int MODE_SMALL       = 1;
+    /**
+     * Mode indicating this slice should be presented in large template format.
+     */
+    public static final int MODE_LARGE       = 2;
+    /**
+     * Mode indicating this slice should be presented as an icon. A shortcut requires an intent,
+     * icon, and label. This can be indicated by using {@link Slice#HINT_TITLE} on an action in a
+     * slice.
+     */
+    public static final int MODE_SHORTCUT    = 3;
+
+    /**
+     * Will select the type of slice binding based on size of the View. TODO: Put in some info about
+     * that selection.
+     */
+    private static final int MODE_AUTO = 0;
+
+    private int mMode = MODE_AUTO;
+    private SliceModeView mCurrentView;
+    private final ActionRow mActions;
+    private Slice mCurrentSlice;
+    private boolean mShowActions = true;
+    private boolean mIsScrollable;
+    private SliceObserver mObserver;
+    private final int mShortcutSize;
+
+    public SliceView(Context context) {
+        this(context, null);
+    }
+
+    public SliceView(Context context, @Nullable AttributeSet attrs) {
+        this(context, attrs, 0);
+    }
+
+    public SliceView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
+        this(context, attrs, defStyleAttr, 0);
+    }
+
+    public SliceView(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
+        super(context, attrs, defStyleAttr, defStyleRes);
+        mObserver = new SliceObserver(new Handler(Looper.getMainLooper()));
+        mActions = new ActionRow(getContext(), true);
+        mActions.setBackground(new ColorDrawable(0xffeeeeee));
+        mCurrentView = new LargeTemplateView(getContext());
+        addView(mCurrentView, getChildLp(mCurrentView));
+        addView(mActions, getChildLp(mActions));
+        mShortcutSize = getContext().getResources()
+                .getDimensionPixelSize(R.dimen.abc_slice_shortcut_size);
+    }
+
+    @Override
+    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
+        int mode = MeasureSpec.getMode(widthMeasureSpec);
+        int width = MeasureSpec.getSize(widthMeasureSpec);
+        if (MODE_SHORTCUT == mMode) {
+            width = mShortcutSize;
+        }
+        if (mode == MeasureSpec.AT_MOST || mode == MeasureSpec.UNSPECIFIED) {
+            widthMeasureSpec = MeasureSpec.makeMeasureSpec(width, MeasureSpec.EXACTLY);
+        }
+        measureChildren(widthMeasureSpec, heightMeasureSpec);
+        int actionHeight = mActions.getVisibility() != View.GONE
+                ? mActions.getMeasuredHeight()
+                : 0;
+        int newHeightSpec = MeasureSpec.makeMeasureSpec(
+                mCurrentView.getMeasuredHeight() + actionHeight, MeasureSpec.EXACTLY);
+        setMeasuredDimension(width, newHeightSpec);
+    }
+
+    @Override
+    protected void onLayout(boolean changed, int l, int t, int r, int b) {
+        mCurrentView.layout(0, 0, mCurrentView.getMeasuredWidth(),
+                mCurrentView.getMeasuredHeight());
+        if (mActions.getVisibility() != View.GONE) {
+            mActions.layout(0, mCurrentView.getMeasuredHeight(), mActions.getMeasuredWidth(),
+                    mCurrentView.getMeasuredHeight() + mActions.getMeasuredHeight());
+        }
+    }
+
+    /**
+     * @hide
+     */
+    @RestrictTo(RestrictTo.Scope.LIBRARY)
+    public void showSlice(Intent intent) {
+        // TODO
+    }
+
+    /**
+     * Populates this view with the {@link Slice} associated with the provided {@link Uri}. To use
+     * this method your app must have the permission
+     * {@link android.Manifest.permission#BIND_SLICE}).
+     * <p>
+     * Setting a slice differs from {@link #showSlice(Slice)} because it will ensure the view is
+     * updated when the slice identified by the provided URI changes. The lifecycle of this observer
+     * is handled by SliceView in {@link #onAttachedToWindow()} and {@link #onDetachedFromWindow()}.
+     * To unregister this observer outside of that you can call {@link #clearSlice}.
+     *
+     * @return true if the a slice was found for the provided uri.
+     * @see #clearSlice
+     */
+    public boolean setSlice(@NonNull Uri sliceUri) {
+        Preconditions.checkNotNull(sliceUri,
+                "Uri cannot be null, to remove the slice use clearSlice()");
+        if (sliceUri == null) {
+            clearSlice();
+            return false;
+        }
+        validate(sliceUri);
+        Slice s = Slice.bindSlice(getContext().getContentResolver(), sliceUri);
+        if (s != null) {
+            if (mObserver != null) {
+                getContext().getContentResolver().unregisterContentObserver(mObserver);
+            }
+            mObserver = new SliceObserver(new Handler(Looper.getMainLooper()));
+            if (isAttachedToWindow()) {
+                registerSlice(sliceUri);
+            }
+            mCurrentSlice = s;
+            reinflate();
+        }
+        return s != null;
+    }
+
+    /**
+     * Populates this view to the provided {@link Slice}.
+     * <p>
+     * This does not register a content observer on the URI that the slice is backed by so it will
+     * not update if the content changes. To have the view update when the content changes use
+     * {@link #setSlice(Uri)} instead. Unlike {@link #setSlice(Uri)}, this method does not require
+     * any special permissions.
+     */
+    public void showSlice(@NonNull Slice slice) {
+        Preconditions.checkNotNull(slice,
+                "Slice cannot be null, to remove the slice use clearSlice()");
+        clearSlice();
+        mCurrentSlice = slice;
+        reinflate();
+    }
+
+    /**
+     * Unregisters the change observer that is set when using {@link #setSlice}. Normally this is
+     * done automatically during {@link #onDetachedFromWindow()}.
+     * <p>
+     * It is safe to call this method multiple times.
+     */
+    public void clearSlice() {
+        mCurrentSlice = null;
+        if (mObserver != null) {
+            getContext().getContentResolver().unregisterContentObserver(mObserver);
+            mObserver = null;
+        }
+    }
+
+    /**
+     * Set the mode this view should present in.
+     */
+    public void setMode(@SliceMode int mode) {
+        setMode(mode, false /* animate */);
+    }
+
+    /**
+     * Set whether this view should allow scrollable content when presenting in {@link #MODE_LARGE}.
+     */
+    public void setScrollable(boolean isScrollable) {
+        mIsScrollable = isScrollable;
+        reinflate();
+    }
+
+    /**
+     * @hide
+     */
+    @RestrictTo(RestrictTo.Scope.LIBRARY)
+    public void setMode(@SliceMode int mode, boolean animate) {
+        if (animate) {
+            Log.e(TAG, "Animation not supported yet");
+        }
+        mMode = mode;
+        reinflate();
+    }
+
+    /**
+     * @return the mode this view is presenting in.
+     */
+    public @SliceMode int getMode() {
+        if (mMode == MODE_AUTO) {
+            return MODE_LARGE;
+        }
+        return mMode;
+    }
+
+    /**
+     * @hide
+     *
+     * Whether this view should show a row of actions with it.
+     */
+    @RestrictTo(RestrictTo.Scope.LIBRARY)
+    public void setShowActionRow(boolean show) {
+        mShowActions = show;
+        reinflate();
+    }
+
+    private SliceModeView createView(int mode) {
+        switch (mode) {
+            case MODE_SHORTCUT:
+                return new ShortcutView(getContext());
+            case MODE_SMALL:
+                return new SmallTemplateView(getContext());
+        }
+        return new LargeTemplateView(getContext());
+    }
+
+    @Override
+    protected void onAttachedToWindow() {
+        super.onAttachedToWindow();
+        registerSlice(mCurrentSlice != null ? mCurrentSlice.getUri() : null);
+    }
+
+    @Override
+    protected void onDetachedFromWindow() {
+        super.onDetachedFromWindow();
+        if (mObserver != null) {
+            getContext().getContentResolver().unregisterContentObserver(mObserver);
+            mObserver = null;
+        }
+    }
+
+    private void registerSlice(Uri sliceUri) {
+        if (sliceUri == null || mObserver == null) {
+            return;
+        }
+        getContext().getContentResolver().registerContentObserver(sliceUri,
+                false /* notifyForDescendants */, mObserver);
+    }
+
+    private void reinflate() {
+        if (mCurrentSlice == null) {
+            return;
+        }
+        // TODO: Smarter mapping here from one state to the next.
+        SliceItem color = SliceQuery.find(mCurrentSlice, SliceItem.TYPE_COLOR);
+        List<SliceItem> items = mCurrentSlice.getItems();
+        SliceItem actionRow = SliceQuery.find(mCurrentSlice, SliceItem.TYPE_SLICE,
+                Slice.HINT_ACTIONS,
+                null);
+        int mode = getMode();
+        if (mode != mCurrentView.getMode()) {
+            removeAllViews();
+            mCurrentView = createView(mode);
+            addView(mCurrentView, getChildLp(mCurrentView));
+            addView(mActions, getChildLp(mActions));
+        }
+        if (mode == MODE_LARGE) {
+            ((LargeTemplateView) mCurrentView).setScrollable(mIsScrollable);
+        }
+        if (items.size() > 1 || (items.size() != 0 && items.get(0) != actionRow)) {
+            mCurrentView.setVisibility(View.VISIBLE);
+            mCurrentView.setSlice(mCurrentSlice);
+        } else {
+            mCurrentView.setVisibility(View.GONE);
+        }
+
+        boolean showActions = mShowActions && actionRow != null
+                && mode != MODE_SHORTCUT;
+        if (showActions) {
+            mActions.setActions(actionRow, color);
+            mActions.setVisibility(View.VISIBLE);
+        } else {
+            mActions.setVisibility(View.GONE);
+        }
+    }
+
+    private LayoutParams getChildLp(View child) {
+        if (child instanceof ShortcutView) {
+            return new LayoutParams(mShortcutSize, mShortcutSize);
+        } else {
+            return new LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.WRAP_CONTENT);
+        }
+    }
+
+    private static void validate(Uri sliceUri) {
+        if (!ContentResolver.SCHEME_CONTENT.equals(sliceUri.getScheme())) {
+            throw new RuntimeException("Invalid uri " + sliceUri);
+        }
+        if (sliceUri.getPathSegments().size() == 0) {
+            throw new RuntimeException("Invalid uri " + sliceUri);
+        }
+    }
+
+    private class SliceObserver extends ContentObserver {
+        SliceObserver(Handler handler) {
+            super(handler);
+        }
+
+        @Override
+        public void onChange(boolean selfChange) {
+            this.onChange(selfChange, null);
+        }
+
+        @Override
+        public void onChange(boolean selfChange, Uri uri) {
+            Slice s = Slice.bindSlice(getContext().getContentResolver(), uri);
+            mCurrentSlice = s;
+            reinflate();
+        }
+    }
+}
diff --git a/slices/view/src/main/java/androidx/app/slice/widget/SliceViewUtil.java b/slices/view/src/main/java/androidx/app/slice/widget/SliceViewUtil.java
new file mode 100644
index 0000000..3b18a9f
--- /dev/null
+++ b/slices/view/src/main/java/androidx/app/slice/widget/SliceViewUtil.java
@@ -0,0 +1,189 @@
+/*
+ * 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 androidx.app.slice.widget;
+
+import android.content.Context;
+import android.content.res.ColorStateList;
+import android.content.res.TypedArray;
+import android.graphics.Bitmap;
+import android.graphics.Bitmap.Config;
+import android.graphics.Canvas;
+import android.graphics.Color;
+import android.graphics.Paint;
+import android.graphics.PorterDuff.Mode;
+import android.graphics.PorterDuffXfermode;
+import android.graphics.Rect;
+import android.graphics.drawable.BitmapDrawable;
+import android.graphics.drawable.Drawable;
+import android.graphics.drawable.Icon;
+import android.support.annotation.AttrRes;
+import android.support.annotation.ColorInt;
+import android.support.annotation.NonNull;
+import android.support.annotation.RestrictTo;
+import android.view.Gravity;
+import android.view.ViewGroup;
+import android.widget.FrameLayout;
+import android.widget.ImageView;
+
+/**
+ * A bunch of utilities for slice UI.
+ *
+ * @hide
+ */
+@RestrictTo(RestrictTo.Scope.LIBRARY)
+public class SliceViewUtil {
+
+    /**
+     */
+    @ColorInt
+    public static int getColorAccent(@NonNull Context context) {
+        return getColorAttr(context, android.R.attr.colorAccent);
+    }
+
+    /**
+     */
+    @ColorInt
+    public static int getColorError(@NonNull Context context) {
+        return getColorAttr(context, android.R.attr.colorError);
+    }
+
+    /**
+     */
+    @ColorInt
+    public static int getDefaultColor(@NonNull Context context, int resId) {
+        final ColorStateList list = context.getResources().getColorStateList(resId,
+                context.getTheme());
+
+        return list.getDefaultColor();
+    }
+
+    /**
+     */
+    @ColorInt
+    public static int getDisabled(@NonNull Context context, int inputColor) {
+        return applyAlphaAttr(context, android.R.attr.disabledAlpha, inputColor);
+    }
+
+    /**
+     */
+    @ColorInt
+    public static int applyAlphaAttr(@NonNull Context context, @AttrRes int attr, int inputColor) {
+        TypedArray ta = context.obtainStyledAttributes(new int[] {
+                attr
+        });
+        float alpha = ta.getFloat(0, 0);
+        ta.recycle();
+        return applyAlpha(alpha, inputColor);
+    }
+
+    /**
+     */
+    @ColorInt
+    public static int applyAlpha(float alpha, int inputColor) {
+        alpha *= Color.alpha(inputColor);
+        return Color.argb((int) (alpha), Color.red(inputColor), Color.green(inputColor),
+                Color.blue(inputColor));
+    }
+
+    /**:%s
+     */
+    @ColorInt
+    public static int getColorAttr(@NonNull Context context, @AttrRes int attr) {
+        TypedArray ta = context.obtainStyledAttributes(new int[] {
+                attr
+        });
+        @ColorInt int colorAccent = ta.getColor(0, 0);
+        ta.recycle();
+        return colorAccent;
+    }
+
+    /**
+     */
+    public static int getThemeAttr(@NonNull Context context, @AttrRes int attr) {
+        TypedArray ta = context.obtainStyledAttributes(new int[] {
+                attr
+        });
+        int theme = ta.getResourceId(0, 0);
+        ta.recycle();
+        return theme;
+    }
+
+    /**
+     */
+    public static Drawable getDrawable(@NonNull Context context, @AttrRes int attr) {
+        TypedArray ta = context.obtainStyledAttributes(new int[] {
+                attr
+        });
+        Drawable drawable = ta.getDrawable(0);
+        ta.recycle();
+        return drawable;
+    }
+
+    /**
+     */
+    public static Icon createIconFromDrawable(Drawable d) {
+        if (d instanceof BitmapDrawable) {
+            return Icon.createWithBitmap(((BitmapDrawable) d).getBitmap());
+        }
+        Bitmap b = Bitmap.createBitmap(d.getIntrinsicWidth(), d.getIntrinsicHeight(),
+                Bitmap.Config.ARGB_8888);
+        Canvas canvas = new Canvas(b);
+        d.setBounds(0, 0, canvas.getWidth(), canvas.getHeight());
+        d.draw(canvas);
+        return Icon.createWithBitmap(b);
+    }
+
+    /**
+     */
+    public static void createCircledIcon(@NonNull Context context, int color, int iconSizePx,
+            Icon icon, boolean isLarge, ViewGroup parent) {
+        ImageView v = new ImageView(context);
+        v.setImageIcon(icon);
+        parent.addView(v);
+        FrameLayout.LayoutParams lp = (FrameLayout.LayoutParams) v.getLayoutParams();
+        if (isLarge) {
+            // XXX better way to convert from icon -> bitmap or crop an icon (?)
+            Bitmap iconBm = Bitmap.createBitmap(iconSizePx, iconSizePx, Config.ARGB_8888);
+            Canvas iconCanvas = new Canvas(iconBm);
+            v.layout(0, 0, iconSizePx, iconSizePx);
+            v.draw(iconCanvas);
+            v.setImageBitmap(getCircularBitmap(iconBm));
+        } else {
+            v.setColorFilter(Color.WHITE);
+        }
+        lp.width = iconSizePx;
+        lp.height = iconSizePx;
+        lp.gravity = Gravity.CENTER;
+    }
+
+    /**
+     */
+    public static @NonNull Bitmap getCircularBitmap(Bitmap bitmap) {
+        Bitmap output = Bitmap.createBitmap(bitmap.getWidth(),
+                bitmap.getHeight(), Config.ARGB_8888);
+        Canvas canvas = new Canvas(output);
+        final Paint paint = new Paint();
+        final Rect rect = new Rect(0, 0, bitmap.getWidth(), bitmap.getHeight());
+        paint.setAntiAlias(true);
+        canvas.drawARGB(0, 0, 0, 0);
+        canvas.drawCircle(bitmap.getWidth() / 2, bitmap.getHeight() / 2,
+                bitmap.getWidth() / 2, paint);
+        paint.setXfermode(new PorterDuffXfermode(Mode.SRC_IN));
+        canvas.drawBitmap(bitmap, rect, rect, paint);
+        return output;
+    }
+}
diff --git a/slices/view/src/main/java/androidx/app/slice/widget/SmallTemplateView.java b/slices/view/src/main/java/androidx/app/slice/widget/SmallTemplateView.java
new file mode 100644
index 0000000..a8c31a9
--- /dev/null
+++ b/slices/view/src/main/java/androidx/app/slice/widget/SmallTemplateView.java
@@ -0,0 +1,228 @@
+/*
+ * 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 androidx.app.slice.widget;
+
+import android.app.PendingIntent.CanceledException;
+import android.app.slice.Slice;
+import android.app.slice.SliceItem;
+import android.content.Context;
+import android.os.AsyncTask;
+import android.support.annotation.RestrictTo;
+import android.view.View;
+import android.widget.ImageView;
+import android.widget.LinearLayout;
+import android.widget.TextView;
+
+import java.text.Format;
+import java.text.SimpleDateFormat;
+import java.util.ArrayList;
+import java.util.Date;
+import java.util.List;
+
+import androidx.app.slice.core.SliceQuery;
+import androidx.app.slice.view.R;
+
+/**
+ * Small template is also used to construct list items for use with {@link LargeTemplateView}.
+ *
+ * @hide
+ */
+@RestrictTo(RestrictTo.Scope.LIBRARY)
+public class SmallTemplateView extends SliceView.SliceModeView implements
+        LargeSliceAdapter.SliceListView {
+
+    private static final String TAG = "SmallTemplateView";
+
+    private int mIconSize;
+    private int mPadding;
+
+    private LinearLayout mStartContainer;
+    private TextView mTitleText;
+    private TextView mSecondaryText;
+    private LinearLayout mEndContainer;
+
+    public SmallTemplateView(Context context) {
+        super(context);
+        inflate(context, R.layout.abc_slice_small_template, this);
+        mIconSize = getContext().getResources().getDimensionPixelSize(R.dimen.abc_slice_icon_size);
+        mPadding = getContext().getResources().getDimensionPixelSize(R.dimen.abc_slice_padding);
+
+        mStartContainer = (LinearLayout) findViewById(android.R.id.icon_frame);
+        mTitleText = (TextView) findViewById(android.R.id.title);
+        mSecondaryText = (TextView) findViewById(android.R.id.summary);
+        mEndContainer = (LinearLayout) findViewById(android.R.id.widget_frame);
+    }
+
+    @Override
+    public @SliceView.SliceMode int getMode() {
+        return SliceView.MODE_SMALL;
+    }
+
+    @Override
+    public void setSliceItem(SliceItem slice) {
+        resetViews();
+        SliceItem colorItem = SliceQuery.find(slice, SliceItem.TYPE_COLOR);
+        List<SliceItem> titleItems = SliceQuery.findAll(slice, -1, Slice.HINT_TITLE,
+                null);
+        int color = setupContent(colorItem, titleItems);
+
+        if (slice.getType() != SliceItem.TYPE_SLICE) {
+            return;
+        }
+
+        // Deal with remaining items
+        ArrayList<SliceItem> sliceItems = new ArrayList<>(
+                slice.getSlice().getItems());
+        bindItems(color, sliceItems);
+    }
+
+    @Override
+    public void setSlice(Slice slice) {
+        resetViews();
+        SliceItem colorItem = SliceQuery.find(slice, SliceItem.TYPE_COLOR);
+        List<SliceItem> titleItems = SliceQuery.findAll(slice, -1, Slice.HINT_TITLE,
+                null);
+        int color = setupContent(colorItem, titleItems);
+
+        // Deal with remaining items
+        ArrayList<SliceItem> sliceItems = new ArrayList<>(slice.getItems());
+        bindItems(color, sliceItems);
+    }
+
+    private void bindItems(int color, ArrayList<SliceItem> sliceItems) {
+        int itemCount = 0;
+        boolean hasSummary = false;
+        for (int i = 0; i < sliceItems.size(); i++) {
+            SliceItem item = sliceItems.get(i);
+            if (!hasSummary && item.getType() == SliceItem.TYPE_TEXT
+                    && !item.hasHint(Slice.HINT_TITLE)) {
+                // TODO -- Should combine all text items?
+                mSecondaryText.setText(item.getText());
+                hasSummary = true;
+            }
+            if (itemCount <= 3) {
+                if (item.getType() == SliceItem.TYPE_ACTION) {
+                    if (addIcon(item, color, mEndContainer)) {
+                        itemCount++;
+                    }
+                } else if (item.getType() == SliceItem.TYPE_IMAGE) {
+                    addIcon(item, color, mEndContainer);
+                    itemCount++;
+                } else if (item.getType() == SliceItem.TYPE_TIMESTAMP) {
+                    TextView tv = new TextView(getContext());
+                    tv.setText(convertTimeToString(item.getTimestamp()));
+                    mEndContainer.addView(tv);
+                    itemCount++;
+                } else if (item.getType() == SliceItem.TYPE_SLICE) {
+                    List<SliceItem> subItems = item.getSlice().getItems();
+                    for (int j = 0; j < subItems.size(); j++) {
+                        sliceItems.add(subItems.get(j));
+                    }
+                }
+            }
+        }
+    }
+
+    private int setupContent(SliceItem colorItem, List<SliceItem> titleItems) {
+        int color = colorItem != null ? colorItem.getColor() : -1;
+
+        // Look for any title elements
+        boolean hasTitleText = false;
+        boolean hasTitleItem = false;
+        for (int i = 0; i < titleItems.size(); i++) {
+            SliceItem item = titleItems.get(i);
+            if (!hasTitleItem) {
+                // icon, action icon, or timestamp
+                if (item.getType() == SliceItem.TYPE_ACTION) {
+                    hasTitleItem = addIcon(item, color, mStartContainer);
+                } else if (item.getType() == SliceItem.TYPE_IMAGE) {
+                    addIcon(item, color, mStartContainer);
+                    hasTitleItem = true;
+                } else if (item.getType() == SliceItem.TYPE_TIMESTAMP) {
+                    TextView tv = new TextView(getContext());
+                    tv.setText(convertTimeToString(item.getTimestamp()));
+                    hasTitleItem = true;
+                }
+            }
+            if (!hasTitleText && item.getType() == SliceItem.TYPE_TEXT) {
+                mTitleText.setText(item.getText());
+                hasTitleText = true;
+            }
+            if (hasTitleText && hasTitleItem) {
+                break;
+            }
+        }
+        mTitleText.setVisibility(hasTitleText ? View.VISIBLE : View.GONE);
+        mStartContainer.setVisibility(hasTitleItem ? View.VISIBLE : View.GONE);
+        return color;
+    }
+
+    /**
+     * @return Whether an icon was added.
+     */
+    private boolean addIcon(SliceItem sliceItem, int color, LinearLayout container) {
+        SliceItem image = null;
+        SliceItem action = null;
+        if (sliceItem.getType() == SliceItem.TYPE_ACTION) {
+            image = SliceQuery.find(sliceItem.getSlice(), SliceItem.TYPE_IMAGE);
+            action = sliceItem;
+        } else if (sliceItem.getType() == SliceItem.TYPE_IMAGE) {
+            image = sliceItem;
+        }
+        if (image != null) {
+            ImageView iv = new ImageView(getContext());
+            iv.setImageIcon(image.getIcon());
+            if (action != null) {
+                final SliceItem sliceAction = action;
+                iv.setOnClickListener(v -> AsyncTask.execute(
+                        () -> {
+                            try {
+                                sliceAction.getAction().send();
+                            } catch (CanceledException e) {
+                                e.printStackTrace();
+                            }
+                        }));
+                iv.setBackground(SliceViewUtil.getDrawable(getContext(),
+                        android.R.attr.selectableItemBackground));
+            }
+            if (color != -1 && !sliceItem.hasHint(Slice.HINT_NO_TINT)) {
+                iv.setColorFilter(color);
+            }
+            container.addView(iv);
+            LinearLayout.LayoutParams lp = (LinearLayout.LayoutParams) iv.getLayoutParams();
+            lp.width = mIconSize;
+            lp.height = mIconSize;
+            lp.setMarginStart(mPadding);
+            return true;
+        }
+        return false;
+    }
+
+    private String convertTimeToString(long time) {
+        // TODO -- figure out what format(s) we support
+        Date date = new Date(time);
+        Format format = new SimpleDateFormat("MM dd yyyy HH:mm:ss");
+        return format.format(date);
+    }
+
+    private void resetViews() {
+        mStartContainer.removeAllViews();
+        mEndContainer.removeAllViews();
+        mTitleText.setText(null);
+        mSecondaryText.setText(null);
+    }
+}
diff --git a/slices/view/src/main/res/drawable/abc_ic_slice_send.xml b/slices/view/src/main/res/drawable/abc_ic_slice_send.xml
new file mode 100644
index 0000000..9c18ac8
--- /dev/null
+++ b/slices/view/src/main/res/drawable/abc_ic_slice_send.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.
+  -->
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+        android:autoMirrored="true"
+        android:width="24.0dp"
+        android:height="24.0dp"
+        android:viewportWidth="48.0"
+        android:viewportHeight="48.0">
+    <path
+        android:fillColor="#FF000000"
+        android:pathData="M4.02,42.0L46.0,24.0 4.02,6.0 4.0,20.0l30.0,4.0 -30.0,4.0z"/>
+</vector>
\ No newline at end of file
diff --git a/slices/view/src/main/res/drawable/abc_slice_remote_input_bg.xml b/slices/view/src/main/res/drawable/abc_slice_remote_input_bg.xml
new file mode 100644
index 0000000..64ac7bf
--- /dev/null
+++ b/slices/view/src/main/res/drawable/abc_slice_remote_input_bg.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.
+  -->
+<shape xmlns:android="http://schemas.android.com/apk/res/android">
+    <solid android:color="#ff6c6c6c" />
+    <corners
+        android:bottomRightRadius="16dp"
+        android:bottomLeftRadius="16dp"/>
+</shape>
diff --git a/slices/view/src/main/res/drawable/abc_slice_ripple_drawable.xml b/slices/view/src/main/res/drawable/abc_slice_ripple_drawable.xml
new file mode 100644
index 0000000..4b97254
--- /dev/null
+++ b/slices/view/src/main/res/drawable/abc_slice_ripple_drawable.xml
@@ -0,0 +1,19 @@
+<?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.
+  -->
+
+<ripple xmlns:android="http://schemas.android.com/apk/res/android"
+    android:color="?android:attr/colorControlHighlight" />
\ No newline at end of file
diff --git a/slices/view/src/main/res/layout/abc_slice_grid.xml b/slices/view/src/main/res/layout/abc_slice_grid.xml
new file mode 100644
index 0000000..1d72a62
--- /dev/null
+++ b/slices/view/src/main/res/layout/abc_slice_grid.xml
@@ -0,0 +1,25 @@
+<?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.
+  -->
+<androidx.app.slice.widget.GridView
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    android:layout_width="match_parent"
+    android:layout_height="wrap_content"
+    android:minHeight="?android:attr/listPreferredItemHeightSmall"
+    android:gravity="center_vertical"
+    android:background="?android:attr/activatedBackgroundIndicator"
+    android:clipToPadding="false">
+</androidx.app.slice.widget.GridView>
diff --git a/slices/view/src/main/res/layout/abc_slice_message.xml b/slices/view/src/main/res/layout/abc_slice_message.xml
new file mode 100644
index 0000000..ee5a85f
--- /dev/null
+++ b/slices/view/src/main/res/layout/abc_slice_message.xml
@@ -0,0 +1,52 @@
+<?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.
+  -->
+<androidx.app.slice.widget.MessageView
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    android:layout_width="match_parent"
+    android:layout_height="wrap_content"
+    android:paddingTop="12dp"
+    android:paddingStart="?android:attr/listPreferredItemPaddingStart"
+    android:paddingEnd="?android:attr/listPreferredItemPaddingEnd"
+    android:background="?android:attr/activatedBackgroundIndicator"
+    android:clipToPadding="false">
+
+    <LinearLayout
+        android:id="@android:id/icon_frame"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:layout_marginStart="-4dp"
+        android:gravity="start|center_vertical"
+        android:orientation="horizontal"
+        android:paddingEnd="12dp"
+        android:paddingTop="4dp"
+        android:paddingBottom="4dp">
+        <!-- TODO: Support text source -->
+        <ImageView
+            android:id="@android:id/icon"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:maxWidth="48dp"
+            android:maxHeight="48dp" />
+    </LinearLayout>
+
+    <TextView android:id="@android:id/summary"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:layout_alignStart="@android:id/title"
+        android:textAppearance="?android:attr/textAppearanceListItem"
+        android:maxLines="10" />
+</androidx.app.slice.widget.MessageView>
diff --git a/slices/view/src/main/res/layout/abc_slice_message_local.xml b/slices/view/src/main/res/layout/abc_slice_message_local.xml
new file mode 100644
index 0000000..d35bd60
--- /dev/null
+++ b/slices/view/src/main/res/layout/abc_slice_message_local.xml
@@ -0,0 +1,39 @@
+<?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.
+  -->
+<androidx.app.slice.widget.MessageView
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    android:layout_width="match_parent"
+    android:layout_height="wrap_content"
+    android:gravity="center_vertical|end"
+    android:paddingTop="12dp"
+    android:paddingStart="?android:attr/listPreferredItemPaddingStart"
+    android:paddingEnd="?android:attr/listPreferredItemPaddingEnd"
+    android:background="?android:attr/activatedBackgroundIndicator"
+    android:clipToPadding="false">
+
+    <TextView android:id="@android:id/summary"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:layout_alignStart="@android:id/title"
+        android:layout_gravity="end"
+        android:gravity="end"
+        android:padding="8dp"
+        android:textAppearance="?android:attr/textAppearanceListItem"
+        android:background="#ffeeeeee"
+        android:maxLines="10" />
+
+</androidx.app.slice.widget.MessageView>
diff --git a/slices/view/src/main/res/layout/abc_slice_remote_input.xml b/slices/view/src/main/res/layout/abc_slice_remote_input.xml
new file mode 100644
index 0000000..293c95a
--- /dev/null
+++ b/slices/view/src/main/res/layout/abc_slice_remote_input.xml
@@ -0,0 +1,77 @@
+<?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 -->
+<androidx.app.slice.widget.RemoteInputView
+        xmlns:android="http://schemas.android.com/apk/res/android"
+        android:id="@+id/remote_input"
+        android:background="@drawable/abc_slice_remote_input_bg"
+        android:layout_height="match_parent"
+        android:layout_width="match_parent">
+
+    <view class="androidx.app.slice.widget.RemoteInputView$RemoteEditText"
+            android:id="@+id/remote_input_text"
+            android:layout_height="match_parent"
+            android:layout_width="0dp"
+            android:layout_weight="1"
+            android:paddingTop="2dp"
+            android:paddingBottom="4dp"
+            android:paddingStart="16dp"
+            android:paddingEnd="12dp"
+            android:gravity="start|center_vertical"
+            android:textAppearance="?android:attr/textAppearance"
+            android:textColor="#FFFFFFFF"
+            android:textColorHint="#99ffffff"
+            android:textSize="16sp"
+            android:background="@null"
+            android:singleLine="true"
+            android:ellipsize="start"
+            android:inputType="textShortMessage|textAutoCorrect|textCapSentences"
+            android:imeOptions="actionSend|flagNoExtractUi|flagNoFullscreen" />
+
+    <FrameLayout
+            android:layout_width="wrap_content"
+            android:layout_height="match_parent"
+            android:layout_gravity="center_vertical">
+
+        <ImageButton
+                android:layout_width="wrap_content"
+                android:layout_height="wrap_content"
+                android:layout_gravity="center"
+                android:paddingStart="12dp"
+                android:paddingEnd="24dp"
+                android:paddingTop="16dp"
+                android:paddingBottom="16dp"
+                android:id="@+id/remote_input_send"
+                android:src="@drawable/abc_ic_slice_send"
+                android:tint="#FFFFFF"
+                android:tintMode="src_in"
+                android:background="@drawable/abc_slice_ripple_drawable" />
+
+        <ProgressBar
+                android:id="@+id/remote_input_progress"
+                android:layout_width="24dp"
+                android:layout_height="24dp"
+                android:layout_marginEnd="6dp"
+                android:layout_gravity="center"
+                android:visibility="invisible"
+                android:indeterminate="true"
+                style="?android:attr/progressBarStyleSmall" />
+
+    </FrameLayout>
+
+</androidx.app.slice.widget.RemoteInputView>
\ No newline at end of file
diff --git a/slices/view/src/main/res/layout/abc_slice_secondary_text.xml b/slices/view/src/main/res/layout/abc_slice_secondary_text.xml
new file mode 100644
index 0000000..b446ddd
--- /dev/null
+++ b/slices/view/src/main/res/layout/abc_slice_secondary_text.xml
@@ -0,0 +1,26 @@
+<?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 -->
+<TextView
+        xmlns:android="http://schemas.android.com/apk/res/android"
+        android:textAppearance="?android:attr/textAppearanceSmall"
+        android:textColor="?android:attr/textColorSecondary"
+        android:gravity="center"
+        android:layout_height="wrap_content"
+        android:padding="4dp"
+        android:layout_width="match_parent" />
diff --git a/slices/view/src/main/res/layout/abc_slice_small_template.xml b/slices/view/src/main/res/layout/abc_slice_small_template.xml
new file mode 100644
index 0000000..ebb2fad
--- /dev/null
+++ b/slices/view/src/main/res/layout/abc_slice_small_template.xml
@@ -0,0 +1,68 @@
+<?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="wrap_content"
+    android:minHeight="?android:attr/listPreferredItemHeightSmall"
+    android:gravity="center_vertical"
+    android:paddingStart="?android:attr/listPreferredItemPaddingStart"
+    android:paddingEnd="?android:attr/listPreferredItemPaddingEnd"
+    android:background="?android:attr/activatedBackgroundIndicator"
+    android:clipToPadding="false">
+
+    <LinearLayout
+        android:id="@android:id/icon_frame"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:layout_marginStart="-4dp"
+        android:gravity="start|center_vertical"
+        android:orientation="horizontal"
+        android:paddingEnd="12dp"
+        android:paddingTop="4dp"
+        android:paddingBottom="4dp"/>
+
+    <LinearLayout
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:layout_weight="1"
+        android:gravity="center_vertical"
+        android:orientation="vertical">
+
+        <TextView android:id="@android:id/title"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:maxLines="2"
+            android:textAppearance="?android:attr/textAppearanceListItem" />
+
+        <TextView android:id="@android:id/summary"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:layout_alignStart="@android:id/title"
+            android:textAppearance="?android:attr/textAppearanceListItemSecondary"
+            android:textColor="?android:attr/textColorSecondary"
+            android:maxLines="10" />
+
+    </LinearLayout>
+
+    <LinearLayout android:id="@android:id/widget_frame"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:gravity="end|center_vertical"
+        android:orientation="horizontal" />
+
+</LinearLayout>
diff --git a/slices/view/src/main/res/layout/abc_slice_title.xml b/slices/view/src/main/res/layout/abc_slice_title.xml
new file mode 100644
index 0000000..e1bdf03
--- /dev/null
+++ b/slices/view/src/main/res/layout/abc_slice_title.xml
@@ -0,0 +1,26 @@
+<?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.
+  -->
+
+<TextView
+        xmlns:android="http://schemas.android.com/apk/res/android"
+        android:textAppearance="?android:attr/textAppearanceMedium"
+        android:textColor="?android:attr/textColorPrimary"
+        android:gravity="center"
+        android:layout_height="wrap_content"
+        android:padding="4dp"
+        android:layout_width="match_parent" />
diff --git a/slices/view/src/main/res/values/dimens.xml b/slices/view/src/main/res/values/dimens.xml
new file mode 100644
index 0000000..c48a215
--- /dev/null
+++ b/slices/view/src/main/res/values/dimens.xml
@@ -0,0 +1,25 @@
+<?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>
+    <!-- Size of a slice shortcut view -->
+    <dimen name="abc_slice_shortcut_size">56dp</dimen>
+    <!-- Size of action icons in a slice -->
+    <dimen name="abc_slice_icon_size">24dp</dimen>
+    <!-- Standard padding used in a slice view -->
+    <dimen name="abc_slice_padding">16dp</dimen>
+</resources>
\ No newline at end of file
diff --git a/slices/view/src/main/res/values/strings.xml b/slices/view/src/main/res/values/strings.xml
new file mode 100644
index 0000000..b72c986
--- /dev/null
+++ b/slices/view/src/main/res/values/strings.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 xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <!-- Format string for indicating there is more content in a slice view -->
+    <string name="abc_slice_more_content">+ <xliff:g id="number" example="5">%1$d</xliff:g></string>
+</resources>
\ No newline at end of file
diff --git a/v14/preference/res/layout-v17/preference_category_material.xml b/v14/preference/res/layout-v17/preference_category_material.xml
index db3abfe..804da6a 100644
--- a/v14/preference/res/layout-v17/preference_category_material.xml
+++ b/v14/preference/res/layout-v17/preference_category_material.xml
@@ -15,13 +15,49 @@
   ~ limitations under the License
   -->
 
-<TextView xmlns:android="http://schemas.android.com/apk/res/android"
-    android:id="@android:id/title"
+<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="wrap_content"
-    android:layout_marginBottom="16dip"
-    android:textAppearance="@style/Preference_TextAppearanceMaterialBody2"
-    android:textColor="@color/preference_fallback_accent_color"
-    android:paddingStart="?android:attr/listPreferredItemPaddingStart"
-    android:paddingEnd="?android:attr/listPreferredItemPaddingEnd"
-    android:paddingTop="16dip" />
+    android:layout_marginBottom="8dp"
+    android:layout_marginTop="8dp"
+    android:paddingStart="?android:attr/listPreferredItemPaddingStart">
+
+    <LinearLayout
+        android:id="@+id/icon_frame"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:gravity="start|center_vertical"
+        android:orientation="horizontal">
+        <android.support.v7.internal.widget.PreferenceImageView
+            android:id="@android:id/icon"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            app:maxHeight="18dp"
+            app:maxWidth="18dp"/>
+    </LinearLayout>
+
+    <LinearLayout
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        android:orientation="vertical"
+        android:paddingStart="56dp">
+        <TextView
+            android:id="@android:id/title"
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:layout_marginTop="16dp"
+            android:paddingEnd="?android:attr/listPreferredItemPaddingEnd"
+            android:textAlignment="viewStart"
+            android:textColor="@color/preference_fallback_accent_color"/>
+        <TextView
+            android:id="@android:id/summary"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:ellipsize="end"
+            android:singleLine="true"
+            android:textColor="?android:attr/textColorSecondary"/>
+    </LinearLayout>
+
+</FrameLayout>
diff --git a/v14/preference/res/layout-v21/preference_category_material.xml b/v14/preference/res/layout-v21/preference_category_material.xml
index dad9a5c..1331268 100644
--- a/v14/preference/res/layout-v21/preference_category_material.xml
+++ b/v14/preference/res/layout-v21/preference_category_material.xml
@@ -15,13 +15,52 @@
   ~ limitations under the License
   -->
 
-<TextView xmlns:android="http://schemas.android.com/apk/res/android"
-    android:id="@android:id/title"
+<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="wrap_content"
-    android:layout_marginBottom="16dip"
-    android:textAppearance="@android:style/TextAppearance.Material.Body2"
-    android:textColor="?android:attr/colorAccent"
-    android:paddingStart="?android:attr/listPreferredItemPaddingStart"
-    android:paddingEnd="?android:attr/listPreferredItemPaddingEnd"
-    android:paddingTop="16dip" />
+    android:layout_marginBottom="8dp"
+    android:layout_marginTop="8dp"
+    android:paddingStart="?android:attr/listPreferredItemPaddingStart">
+
+    <LinearLayout
+        android:id="@+id/icon_frame"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:gravity="start|center_vertical"
+        android:orientation="horizontal">
+        <android.support.v7.internal.widget.PreferenceImageView
+            android:id="@android:id/icon"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            app:maxHeight="18dp"
+            app:maxWidth="18dp"
+            android:tint="?android:attr/textColorPrimary"/>
+    </LinearLayout>
+
+    <LinearLayout
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        android:orientation="vertical"
+        android:paddingStart="56dp">
+        <TextView
+            android:id="@android:id/title"
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:layout_marginTop="16dp"
+            android:paddingEnd="?android:attr/listPreferredItemPaddingEnd"
+            android:textAlignment="viewStart"
+            android:textAppearance="@android:style/TextAppearance.Material.Body2"
+            android:textColor="?android:attr/colorAccent"/>
+        <TextView
+            android:id="@android:id/summary"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:ellipsize="end"
+            android:singleLine="true"
+            android:textAppearance="?android:attr/textAppearanceListItemSecondary"
+            android:textColor="?android:attr/textColorSecondary"/>
+    </LinearLayout>
+
+</FrameLayout>
diff --git a/v14/preference/res/layout-v21/preference_dropdown_material.xml b/v14/preference/res/layout-v21/preference_dropdown_material.xml
index a92095e..f886d88 100644
--- a/v14/preference/res/layout-v21/preference_dropdown_material.xml
+++ b/v14/preference/res/layout-v21/preference_dropdown_material.xml
@@ -15,74 +15,18 @@
   ~ limitations under the License
   -->
 
-<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
-    xmlns:app="http://schemas.android.com/apk/res-auto"
+<FrameLayout
+    xmlns:android="http://schemas.android.com/apk/res/android"
     android:layout_width="match_parent"
-    android:layout_height="wrap_content"
-    android:minHeight="?android:attr/listPreferredItemHeightSmall"
-    android:gravity="center_vertical"
-    android:paddingLeft="?android:attr/listPreferredItemPaddingLeft"
-    android:paddingRight="?android:attr/listPreferredItemPaddingRight"
-    android:background="?android:attr/selectableItemBackground"
-    android:clipToPadding="false"
-    android:focusable="true" >
+    android:layout_height="wrap_content">
 
     <Spinner
         android:id="@+id/spinner"
         android:layout_width="0dp"
         android:layout_height="wrap_content"
+        android:layout_marginStart="@dimen/preference_no_icon_padding_start"
         android:visibility="invisible" />
 
-    <LinearLayout
-        android:id="@+id/icon_frame"
-        android:layout_width="wrap_content"
-        android:layout_height="wrap_content"
-        android:layout_marginLeft="-4dp"
-        android:minWidth="60dp"
-        android:gravity="start|center_vertical"
-        android:orientation="horizontal"
-        android:paddingRight="12dp"
-        android:paddingTop="4dp"
-        android:paddingBottom="4dp">
-        <android.support.v7.internal.widget.PreferenceImageView
-            android:id="@android:id/icon"
-            android:layout_width="wrap_content"
-            android:layout_height="wrap_content"
-            app:maxWidth="48dp"
-            app:maxHeight="48dp" />
-    </LinearLayout>
+    <include layout="@layout/preference_material"/>
 
-    <RelativeLayout
-        android:layout_width="wrap_content"
-        android:layout_height="wrap_content"
-        android:layout_weight="1"
-        android:paddingTop="16dp"
-        android:paddingBottom="16dp">
-
-        <TextView android:id="@android:id/title"
-            android:layout_width="wrap_content"
-            android:layout_height="wrap_content"
-            android:singleLine="true"
-            android:textAppearance="@style/Preference_TextAppearanceMaterialSubhead"
-            android:ellipsize="marquee" />
-
-        <TextView android:id="@android:id/summary"
-            android:layout_width="wrap_content"
-            android:layout_height="wrap_content"
-            android:layout_below="@android:id/title"
-            android:layout_alignStart="@android:id/title"
-            android:textAppearance="?android:attr/textAppearanceSmall"
-            android:textColor="?android:attr/textColorSecondary"
-            android:maxLines="10" />
-
-    </RelativeLayout>
-
-    <!-- Preference should place its actual preference widget here. -->
-    <LinearLayout android:id="@android:id/widget_frame"
-        android:layout_width="wrap_content"
-        android:layout_height="match_parent"
-        android:gravity="end|center_vertical"
-        android:paddingLeft="16dp"
-        android:orientation="vertical" />
-
-</LinearLayout>
+</FrameLayout>
diff --git a/v14/preference/res/layout-v21/preference_material.xml b/v14/preference/res/layout-v21/preference_material.xml
index da6b69f..197e429 100644
--- a/v14/preference/res/layout-v21/preference_material.xml
+++ b/v14/preference/res/layout-v21/preference_material.xml
@@ -31,11 +31,10 @@
         android:id="@+id/icon_frame"
         android:layout_width="wrap_content"
         android:layout_height="wrap_content"
-        android:layout_marginStart="-4dp"
-        android:minWidth="60dp"
+        android:minWidth="56dp"
         android:gravity="start|center_vertical"
         android:orientation="horizontal"
-        android:paddingEnd="12dp"
+        android:paddingEnd="8dp"
         android:paddingTop="4dp"
         android:paddingBottom="4dp">
         <android.support.v7.internal.widget.PreferenceImageView
diff --git a/v14/preference/res/layout/preference_category_material.xml b/v14/preference/res/layout/preference_category_material.xml
index e366e7a..8eb2137 100644
--- a/v14/preference/res/layout/preference_category_material.xml
+++ b/v14/preference/res/layout/preference_category_material.xml
@@ -15,13 +15,49 @@
   ~ limitations under the License
   -->
 
-<TextView xmlns:android="http://schemas.android.com/apk/res/android"
-    android:id="@android:id/title"
+<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="wrap_content"
-    android:layout_marginBottom="16dip"
-    android:textAppearance="@style/Preference_TextAppearanceMaterialBody2"
-    android:textColor="@color/preference_fallback_accent_color"
-    android:paddingLeft="?android:attr/listPreferredItemPaddingLeft"
-    android:paddingRight="?android:attr/listPreferredItemPaddingRight"
-    android:paddingTop="16dip" />
+    android:layout_marginBottom="8dp"
+    android:layout_marginTop="8dp"
+    android:paddingLeft="?android:attr/listPreferredItemPaddingLeft">
+
+    <LinearLayout
+        android:id="@+id/icon_frame"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:gravity="start|center_vertical"
+        android:orientation="horizontal">
+        <android.support.v7.internal.widget.PreferenceImageView
+            android:id="@android:id/icon"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            app:maxHeight="18dp"
+            app:maxWidth="18dp"/>
+    </LinearLayout>
+
+    <LinearLayout
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        android:orientation="vertical"
+        android:paddingLeft="56dp">
+        <TextView
+            android:id="@android:id/title"
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:layout_marginTop="16dp"
+            android:paddingRight="?android:attr/listPreferredItemPaddingRight"
+            android:textAlignment="viewStart"
+            android:textColor="@color/preference_fallback_accent_color"/>
+        <TextView
+            android:id="@android:id/summary"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:ellipsize="end"
+            android:singleLine="true"
+            android:textColor="?android:attr/textColorSecondary"/>
+    </LinearLayout>
+
+</FrameLayout>
diff --git a/v14/preference/res/values-v21/styles.xml b/v14/preference/res/values-v21/styles.xml
new file mode 100644
index 0000000..9a85987
--- /dev/null
+++ b/v14/preference/res/values-v21/styles.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>
+    <dimen name="preference_no_icon_padding_start">72dp</dimen>
+</resources>
+
diff --git a/v14/preference/res/values/styles.xml b/v14/preference/res/values/styles.xml
index 26b1544..edd5285 100644
--- a/v14/preference/res/values/styles.xml
+++ b/v14/preference/res/values/styles.xml
@@ -24,6 +24,10 @@
 
     <style name="Preference.Material">
         <item name="android:layout">@layout/preference_material</item>
+        <item name="allowDividerAbove">false</item>
+        <item name="allowDividerBelow">true</item>
+        <item name="singleLineTitle">false</item>
+        <item name="iconSpaceReserved">true</item>
     </style>
 
     <style name="Preference.Information.Material">
@@ -34,10 +38,16 @@
 
     <style name="Preference.Category.Material">
         <item name="android:layout">@layout/preference_category_material</item>
+        <item name="allowDividerAbove">true</item>
+        <item name="allowDividerBelow">true</item>
+        <item name="iconSpaceReserved">true</item>
     </style>
 
     <style name="Preference.CheckBoxPreference.Material">
         <item name="android:layout">@layout/preference_material</item>
+        <item name="allowDividerAbove">false</item>
+        <item name="allowDividerBelow">true</item>
+        <item name="iconSpaceReserved">true</item>
     </style>
 
     <style name="Preference.SwitchPreferenceCompat.Material">
@@ -46,6 +56,10 @@
 
     <style name="Preference.SwitchPreference.Material">
         <item name="android:layout">@layout/preference_material</item>
+        <item name="allowDividerAbove">false</item>
+        <item name="allowDividerBelow">true</item>
+        <item name="singleLineTitle">false</item>
+        <item name="iconSpaceReserved">true</item>
     </style>
 
     <style name="Preference.SeekBarPreference.Material">
@@ -56,18 +70,31 @@
 
     <style name="Preference.PreferenceScreen.Material">
         <item name="android:layout">@layout/preference_material</item>
+        <item name="allowDividerAbove">false</item>
+        <item name="allowDividerBelow">true</item>
+        <item name="iconSpaceReserved">true</item>
     </style>
 
     <style name="Preference.DialogPreference.Material">
         <item name="android:layout">@layout/preference_material</item>
+        <item name="allowDividerAbove">false</item>
+        <item name="allowDividerBelow">true</item>
+        <item name="iconSpaceReserved">true</item>
     </style>
 
     <style name="Preference.DialogPreference.EditTextPreference.Material">
         <item name="android:layout">@layout/preference_material</item>
+        <item name="allowDividerAbove">false</item>
+        <item name="allowDividerBelow">true</item>
+        <item name="singleLineTitle">false</item>
+        <item name="iconSpaceReserved">true</item>
     </style>
 
     <style name="Preference.DropDown.Material">
         <item name="android:layout">@layout/preference_dropdown_material</item>
+        <item name="allowDividerAbove">false</item>
+        <item name="allowDividerBelow">true</item>
+        <item name="iconSpaceReserved">true</item>
     </style>
 
     <style name="Preference_TextAppearanceMaterialBody2">
@@ -86,6 +113,7 @@
 
     <style name="PreferenceFragment.Material">
         <item name="android:divider">@drawable/preference_list_divider_material</item>
+        <item name="allowDividerAfterLastItem">false</item>
     </style>
 
     <style name="PreferenceFragmentList.Material">
diff --git a/v14/preference/res/values/themes.xml b/v14/preference/res/values/themes.xml
index a69126f..919873e 100644
--- a/v14/preference/res/values/themes.xml
+++ b/v14/preference/res/values/themes.xml
@@ -36,5 +36,6 @@
         <item name="editTextPreferenceStyle">@style/Preference.DialogPreference.EditTextPreference.Material</item>
         <item name="dropdownPreferenceStyle">@style/Preference.DropDown.Material</item>
         <item name="preferenceFragmentListStyle">@style/PreferenceFragmentList.Material</item>
+        <item name="android:scrollbars">vertical</item>
     </style>
 </resources>
diff --git a/v17/leanback/res/values-mr/strings.xml b/v17/leanback/res/values-mr/strings.xml
index 8629ef1..1fcb920 100644
--- a/v17/leanback/res/values-mr/strings.xml
+++ b/v17/leanback/res/values-mr/strings.xml
@@ -47,7 +47,7 @@
     <string name="lb_playback_controls_high_quality_disable" msgid="8637371582779057866">"उच्च गुणवत्ता अक्षम करा"</string>
     <string name="lb_playback_controls_closed_captioning_enable" msgid="2429655367176440226">"उपशीर्षके सक्षम करा"</string>
     <string name="lb_playback_controls_closed_captioning_disable" msgid="6133362019475930048">"सबटायटल अक्षम करा"</string>
-    <string name="lb_playback_controls_picture_in_picture" msgid="3040035547765350690">"चित्र मोडमध्ये चित्र प्रविष्ट करा"</string>
+    <string name="lb_playback_controls_picture_in_picture" msgid="3040035547765350690">"चित्र मोडमध्ये चित्र एंटर करा"</string>
     <string name="lb_playback_time_separator" msgid="3208380806582304911">"/"</string>
     <string name="lb_playback_controls_shown" msgid="6382160135512023238">"मीडिया नियंत्रणे दर्शवली आहेत"</string>
     <string name="lb_playback_controls_hidden" msgid="8940984081242033574">"मीडिया नियंत्रणे लपलेली आहेत, दर्शवण्‍यासाठी d-pad दाबा"</string>
diff --git a/v7/appcompat/res/values-vi/strings.xml b/v7/appcompat/res/values-vi/strings.xml
index 9587bed..4560b4b 100644
--- a/v7/appcompat/res/values-vi/strings.xml
+++ b/v7/appcompat/res/values-vi/strings.xml
@@ -19,7 +19,7 @@
     <string name="abc_action_mode_done" msgid="4076576682505996667">"Xong"</string>
     <string name="abc_action_bar_home_description" msgid="4600421777120114993">"Điều hướng về trang chủ"</string>
     <string name="abc_action_bar_up_description" msgid="1594238315039666878">"Điều hướng lên trên"</string>
-    <string name="abc_action_menu_overflow_description" msgid="3588849162933574182">"Thêm tùy chọn"</string>
+    <string name="abc_action_menu_overflow_description" msgid="3588849162933574182">"Tùy chọn khác"</string>
     <string name="abc_toolbar_collapse_description" msgid="1603543279005712093">"Thu gọn"</string>
     <string name="abc_searchview_description_search" msgid="8264924765203268293">"Tìm kiếm"</string>
     <string name="abc_search_hint" msgid="7723749260725869598">"Tìm kiếm…"</string>
diff --git a/v7/appcompat/src/main/java/android/support/v7/widget/TooltipCompatHandler.java b/v7/appcompat/src/main/java/android/support/v7/widget/TooltipCompatHandler.java
index 5ce1f8b..63a6198 100644
--- a/v7/appcompat/src/main/java/android/support/v7/widget/TooltipCompatHandler.java
+++ b/v7/appcompat/src/main/java/android/support/v7/widget/TooltipCompatHandler.java
@@ -66,6 +66,10 @@
     private TooltipPopup mPopup;
     private boolean mFromTouch;
 
+    // The handler currently scheduled to show a tooltip, triggered by a hover
+    // (there can be only one).
+    private static TooltipCompatHandler sPendingHandler;
+
     // The handler currently showing a tooltip (there can be only one).
     private static TooltipCompatHandler sActiveHandler;
 
@@ -76,6 +80,15 @@
      * @param tooltipText the tooltip text
      */
     public static void setTooltipText(View view, CharSequence tooltipText) {
+        // The code below is not attempting to update the tooltip text
+        // for a pending or currently active tooltip, because it may lead
+        // to updating the wrong tooltip in in some rare cases (e.g. when
+        // action menu item views are recycled). Instead, the tooltip is
+        // canceled/hidden. This might still be the wrong tooltip,
+        // but hiding a wrong tooltip is less disruptive UX.
+        if (sPendingHandler != null && sPendingHandler.mAnchor == view) {
+            setPendingHandler(null);
+        }
         if (TextUtils.isEmpty(tooltipText)) {
             if (sActiveHandler != null && sActiveHandler.mAnchor == view) {
                 sActiveHandler.hide();
@@ -119,8 +132,7 @@
                 if (mAnchor.isEnabled() && mPopup == null) {
                     mAnchorX = (int) event.getX();
                     mAnchorY = (int) event.getY();
-                    mAnchor.removeCallbacks(mShowRunnable);
-                    mAnchor.postDelayed(mShowRunnable, ViewConfiguration.getLongPressTimeout());
+                    setPendingHandler(this);
                 }
                 break;
             case MotionEvent.ACTION_HOVER_EXIT:
@@ -145,6 +157,7 @@
         if (!ViewCompat.isAttachedToWindow(mAnchor)) {
             return;
         }
+        setPendingHandler(null);
         if (sActiveHandler != null) {
             sActiveHandler.hide();
         }
@@ -180,7 +193,27 @@
                 Log.e(TAG, "sActiveHandler.mPopup == null");
             }
         }
-        mAnchor.removeCallbacks(mShowRunnable);
+        if (sPendingHandler == this) {
+            setPendingHandler(null);
+        }
         mAnchor.removeCallbacks(mHideRunnable);
     }
+
+    private static void setPendingHandler(TooltipCompatHandler handler) {
+        if (sPendingHandler != null) {
+            sPendingHandler.cancelPendingShow();
+        }
+        sPendingHandler = handler;
+        if (sPendingHandler != null) {
+            sPendingHandler.scheduleShow();
+        }
+    }
+
+    private void scheduleShow() {
+        mAnchor.postDelayed(mShowRunnable, ViewConfiguration.getLongPressTimeout());
+    }
+
+    private void cancelPendingShow() {
+        mAnchor.removeCallbacks(mShowRunnable);
+    }
 }
diff --git a/v7/appcompat/tests/src/android/support/v7/app/NightModeTestCase.java b/v7/appcompat/tests/src/android/support/v7/app/NightModeTestCase.java
index 8ae5b24..d42174f 100644
--- a/v7/appcompat/tests/src/android/support/v7/app/NightModeTestCase.java
+++ b/v7/appcompat/tests/src/android/support/v7/app/NightModeTestCase.java
@@ -23,12 +23,15 @@
 import static android.support.v7.app.NightModeActivity.TOP_ACTIVITY;
 import static android.support.v7.testutils.TestUtilsMatchers.isBackground;
 
+import static org.junit.Assert.assertTrue;
+
 import android.app.Instrumentation;
 import android.support.test.InstrumentationRegistry;
 import android.support.test.filters.LargeTest;
 import android.support.test.rule.ActivityTestRule;
 import android.support.test.runner.AndroidJUnit4;
 import android.support.testutils.AppCompatActivityUtils;
+import android.support.testutils.RecreatedAppCompatActivity;
 import android.support.v4.content.ContextCompat;
 import android.support.v7.appcompat.test.R;
 
@@ -37,6 +40,9 @@
 import org.junit.Test;
 import org.junit.runner.RunWith;
 
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.TimeUnit;
+
 @LargeTest
 @RunWith(AndroidJUnit4.class)
 public class NightModeTestCase {
@@ -103,13 +109,13 @@
         // Verify that we're currently in day mode
         onView(withId(R.id.text_night_mode)).check(matches(withText(STRING_DAY)));
 
-        // Now set MODE_NIGHT_AUTO so that we will change to night mode automatically
+        // Set MODE_NIGHT_AUTO so that we will change to night mode automatically
         final NightModeActivity newActivity =
                 setLocalNightModeAndWaitForRecreate(activity, AppCompatDelegate.MODE_NIGHT_AUTO);
         final AppCompatDelegateImplV14 newDelegate =
                 (AppCompatDelegateImplV14) newActivity.getDelegate();
 
-        // Now update the fake twilight manager to be in night and trigger a fake 'time' change
+        // Update the fake twilight manager to be in night and trigger a fake 'time' change
         mActivityTestRule.runOnUiThread(new Runnable() {
             @Override
             public void run() {
@@ -118,10 +124,12 @@
             }
         });
 
-        // At this point recreate has been completed
-        InstrumentationRegistry.getInstrumentation().waitForIdleSync();
+        RecreatedAppCompatActivity.sResumed = new CountDownLatch(1);
+        assertTrue(RecreatedAppCompatActivity.sResumed.await(1, TimeUnit.SECONDS));
+        // At this point recreate that has been triggered by dispatchTimeChanged call
+        // has completed
 
-        // Now check that the text has changed, signifying that night resources are being used
+        // Check that the text has changed, signifying that night resources are being used
         onView(withId(R.id.text_night_mode)).check(matches(withText(STRING_NIGHT)));
     }
 
diff --git a/v7/mediarouter/res/values-in/strings.xml b/v7/mediarouter/res/values-in/strings.xml
index 5e537af..e76b3e8 100644
--- a/v7/mediarouter/res/values-in/strings.xml
+++ b/v7/mediarouter/res/values-in/strings.xml
@@ -18,10 +18,10 @@
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="mr_system_route_name" msgid="5441529851481176817">"Sistem"</string>
     <string name="mr_user_route_category_name" msgid="7498112907524977311">"Perangkat"</string>
-    <string name="mr_button_content_description" msgid="3698378085901466129">"Tombol transmisi"</string>
-    <string name="mr_cast_button_disconnected" msgid="816305490427819240">"Tombol transmisi. Terputus"</string>
-    <string name="mr_cast_button_connecting" msgid="2187642765091873834">"Tombol transmisi. Menghubungkan"</string>
-    <string name="mr_cast_button_connected" msgid="5088427771788648085">"Tombol transmisi. Terhubung"</string>
+    <string name="mr_button_content_description" msgid="3698378085901466129">"Tombol Cast"</string>
+    <string name="mr_cast_button_disconnected" msgid="816305490427819240">"Tombol Cast. Terputus"</string>
+    <string name="mr_cast_button_connecting" msgid="2187642765091873834">"Tombol Cast. Menghubungkan"</string>
+    <string name="mr_cast_button_connected" msgid="5088427771788648085">"Tombol Cast. Terhubung"</string>
     <string name="mr_chooser_title" msgid="414301941546135990">"Transmisikan ke"</string>
     <string name="mr_chooser_searching" msgid="6349900579507521956">"Mencari perangkat"</string>
     <string name="mr_controller_disconnect" msgid="1227264889412989580">"Putuskan sambungan"</string>
diff --git a/v7/mediarouter/res/values-ml/strings.xml b/v7/mediarouter/res/values-ml/strings.xml
index c7b50be..62258fb 100644
--- a/v7/mediarouter/res/values-ml/strings.xml
+++ b/v7/mediarouter/res/values-ml/strings.xml
@@ -26,7 +26,7 @@
     <string name="mr_chooser_searching" msgid="6349900579507521956">"ഉപകരണങ്ങൾ കണ്ടെത്തുന്നു"</string>
     <string name="mr_controller_disconnect" msgid="1227264889412989580">"വിച്ഛേദിക്കുക"</string>
     <string name="mr_controller_stop_casting" msgid="8857886794086583226">"കാസ്റ്റുചെയ്യൽ നിർത്തുക"</string>
-    <string name="mr_controller_close_description" msgid="7333862312480583260">"അടയ്‌ക്കുക"</string>
+    <string name="mr_controller_close_description" msgid="7333862312480583260">"അവസാനിപ്പിക്കുക"</string>
     <string name="mr_controller_play" msgid="683634565969987458">"പ്ലേ ചെയ്യുക"</string>
     <string name="mr_controller_pause" msgid="5451884435510905406">"തൽക്കാലം നിർത്തൂ"</string>
     <string name="mr_controller_stop" msgid="735874641921425123">"നിര്‍ത്തുക"</string>
diff --git a/v7/mediarouter/res/values-mr/strings.xml b/v7/mediarouter/res/values-mr/strings.xml
index 27923d1..596b56a 100644
--- a/v7/mediarouter/res/values-mr/strings.xml
+++ b/v7/mediarouter/res/values-mr/strings.xml
@@ -20,7 +20,7 @@
     <string name="mr_user_route_category_name" msgid="7498112907524977311">"डिव्हाइसेस"</string>
     <string name="mr_button_content_description" msgid="3698378085901466129">"कास्ट बटण"</string>
     <string name="mr_cast_button_disconnected" msgid="816305490427819240">"कास्ट बटण. डिस्कनेक्ट केले"</string>
-    <string name="mr_cast_button_connecting" msgid="2187642765091873834">"कास्ट बटण. कनेक्ट करीत आहे"</string>
+    <string name="mr_cast_button_connecting" msgid="2187642765091873834">"कास्ट बटण. कनेक्ट करत आहे"</string>
     <string name="mr_cast_button_connected" msgid="5088427771788648085">"कास्ट बटण. कनेक्ट केले"</string>
     <string name="mr_chooser_title" msgid="414301941546135990">"यावर कास्ट करा"</string>
     <string name="mr_chooser_searching" msgid="6349900579507521956">"डिव्हाइसेस शोधत आहे"</string>
@@ -36,5 +36,5 @@
     <string name="mr_controller_volume_slider" msgid="2361785992211841709">"व्हॉल्यूम स्लायडर"</string>
     <string name="mr_controller_no_media_selected" msgid="6547130360349182381">"मीडिया निवडला नाही"</string>
     <string name="mr_controller_no_info_available" msgid="5585418471741142924">"कोणतीही माहिती उपलब्ध नाही"</string>
-    <string name="mr_controller_casting_screen" msgid="4868457957151124867">"स्क्रीन कास्‍ट करीत आहे"</string>
+    <string name="mr_controller_casting_screen" msgid="4868457957151124867">"स्क्रीन कास्‍ट करत आहे"</string>
 </resources>
diff --git a/v7/mediarouter/res/values-ta/strings.xml b/v7/mediarouter/res/values-ta/strings.xml
index 99c6172..9888472 100644
--- a/v7/mediarouter/res/values-ta/strings.xml
+++ b/v7/mediarouter/res/values-ta/strings.xml
@@ -22,7 +22,7 @@
     <string name="mr_cast_button_disconnected" msgid="816305490427819240">"அனுப்புதல் பொத்தான். துண்டிக்கப்பட்டது"</string>
     <string name="mr_cast_button_connecting" msgid="2187642765091873834">"அனுப்புதல் பொத்தான். இணைக்கிறது"</string>
     <string name="mr_cast_button_connected" msgid="5088427771788648085">"அனுப்புதல் பொத்தான். இணைக்கப்பட்டது"</string>
-    <string name="mr_chooser_title" msgid="414301941546135990">"இதில் திரையிடு"</string>
+    <string name="mr_chooser_title" msgid="414301941546135990">"இதற்கு அனுப்பு"</string>
     <string name="mr_chooser_searching" msgid="6349900579507521956">"சாதனங்களைத் தேடுகிறது"</string>
     <string name="mr_controller_disconnect" msgid="1227264889412989580">"தொடர்பைத் துண்டி"</string>
     <string name="mr_controller_stop_casting" msgid="8857886794086583226">"அனுப்புவதை நிறுத்து"</string>
diff --git a/v7/mediarouter/res/values-tr/strings.xml b/v7/mediarouter/res/values-tr/strings.xml
index a0eb2e4..8189092 100644
--- a/v7/mediarouter/res/values-tr/strings.xml
+++ b/v7/mediarouter/res/values-tr/strings.xml
@@ -18,7 +18,7 @@
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="mr_system_route_name" msgid="5441529851481176817">"Sistem"</string>
     <string name="mr_user_route_category_name" msgid="7498112907524977311">"Cihazlar"</string>
-    <string name="mr_button_content_description" msgid="3698378085901466129">"Yayın düğmesi"</string>
+    <string name="mr_button_content_description" msgid="3698378085901466129">"Yayınla düğmesi"</string>
     <string name="mr_cast_button_disconnected" msgid="816305490427819240">"Yayınla düğmesi. Bağlantı kesildi"</string>
     <string name="mr_cast_button_connecting" msgid="2187642765091873834">"Yayınla düğmesi. Bağlanıyor"</string>
     <string name="mr_cast_button_connected" msgid="5088427771788648085">"Yayınla düğmesi. Bağlandı"</string>
diff --git a/v7/recyclerview/tests/Android.mk b/v7/recyclerview/tests/Android.mk
deleted file mode 100644
index c6299d7..0000000
--- a/v7/recyclerview/tests/Android.mk
+++ /dev/null
@@ -1,40 +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.
-
-LOCAL_PATH := $(call my-dir)
-
-include $(CLEAR_VARS)
-
-LOCAL_MODULE_TAGS := tests
-
-LOCAL_SDK_VERSION := $(SUPPORT_CURRENT_SDK_VERSION)
-
-LOCAL_SRC_FILES := $(call all-java-files-under, src)
-
-LOCAL_RESOURCE_DIR := \
-    $(LOCAL_PATH)/res \
-    $(LOCAL_PATH)/../res
-
-LOCAL_STATIC_JAVA_LIBRARIES  := \
-        android-support-v7-recyclerview \
-        android-support-v4 \
-        android-support-annotations
-
-LOCAL_PACKAGE_NAME := RecyclerViewTests
-LOCAL_COMPATIBILITY_SUITE := device-tests
-LOCAL_AAPT_FLAGS := \
-        --auto-add-overlay \
-        --extra-packages android.support.v7.recyclerview
-
-include $(BUILD_PACKAGE)