Add Commit Content samples to nyc-dev

Samples for new image keyboard / commit content API added in API 25.

Change-Id: Ie642eadbb57988b2987b4f3598cd5f5f803a0e7b
diff --git a/build.gradle b/build.gradle
index 6d1225d..1f100b3 100644
--- a/build.gradle
+++ b/build.gradle
@@ -119,6 +119,8 @@
 "ui/window/DragAndDropAcrossApps",
 "wearable/wear/WearNotifications",
 "system/AppShortcuts",
+"input/keyboard/CommitContentSampleApp",
+"input/keyboard/CommitContentSampleIME",
 ]
 
 List<String> taskNames = [
diff --git a/input/keyboard/CommitContentSampleApp/.gitignore b/input/keyboard/CommitContentSampleApp/.gitignore
new file mode 100644
index 0000000..39fb081
--- /dev/null
+++ b/input/keyboard/CommitContentSampleApp/.gitignore
@@ -0,0 +1,9 @@
+*.iml
+.gradle
+/local.properties
+/.idea/workspace.xml
+/.idea/libraries
+.DS_Store
+/build
+/captures
+.externalNativeBuild
diff --git a/input/keyboard/CommitContentSampleApp/app/.gitignore b/input/keyboard/CommitContentSampleApp/app/.gitignore
new file mode 100644
index 0000000..796b96d
--- /dev/null
+++ b/input/keyboard/CommitContentSampleApp/app/.gitignore
@@ -0,0 +1 @@
+/build
diff --git a/input/keyboard/CommitContentSampleApp/app/build.gradle b/input/keyboard/CommitContentSampleApp/app/build.gradle
new file mode 100644
index 0000000..f46fe08
--- /dev/null
+++ b/input/keyboard/CommitContentSampleApp/app/build.gradle
@@ -0,0 +1,30 @@
+apply plugin: 'com.android.application'
+
+android {
+    compileSdkVersion 25
+    buildToolsVersion "25 rc1"
+    defaultConfig {
+        applicationId "com.example.android.android.sampleapp"
+        minSdkVersion 16
+        targetSdkVersion 25
+        versionCode 1
+        versionName "1.0"
+        testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
+    }
+    buildTypes {
+        release {
+            minifyEnabled false
+            proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
+        }
+    }
+}
+
+dependencies {
+    compile fileTree(include: ['*.jar'], dir: 'libs')
+    androidTestCompile('com.android.support.test.espresso:espresso-core:2.2.2', {
+        exclude group: 'com.android.support', module: 'support-annotations'
+    })
+    compile 'com.android.support:appcompat-v7:25.0.0'
+    compile 'com.android.support:support-v13:25.0.0'
+    testCompile 'junit:junit:4.12'
+}
diff --git a/input/keyboard/CommitContentSampleApp/app/proguard-rules.pro b/input/keyboard/CommitContentSampleApp/app/proguard-rules.pro
new file mode 100644
index 0000000..19b873a
--- /dev/null
+++ b/input/keyboard/CommitContentSampleApp/app/proguard-rules.pro
@@ -0,0 +1,17 @@
+# Add project specific ProGuard rules here.
+# By default, the flags in this file are appended to flags specified
+# in /usr/local/google/home/yukawa/Android/Sdk/tools/proguard/proguard-android.txt
+# You can edit the include path and order by changing the proguardFiles
+# directive in build.gradle.
+#
+# For more details, see
+#   http://developer.android.com/guide/developing/tools/proguard.html
+
+# Add any project specific keep options here:
+
+# If your project uses WebView with JS, uncomment the following
+# and specify the fully qualified class name to the JavaScript interface
+# class:
+#-keepclassmembers class fqcn.of.javascript.interface.for.webview {
+#   public *;
+#}
diff --git a/input/keyboard/CommitContentSampleApp/app/src/main/AndroidManifest.xml b/input/keyboard/CommitContentSampleApp/app/src/main/AndroidManifest.xml
new file mode 100644
index 0000000..16c93b1
--- /dev/null
+++ b/input/keyboard/CommitContentSampleApp/app/src/main/AndroidManifest.xml
@@ -0,0 +1,18 @@
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+    package="com.example.android.commitcontent.app">
+
+    <application
+        android:allowBackup="true"
+        android:icon="@mipmap/ic_launcher"
+        android:label="@string/app_name"
+        android:supportsRtl="true"
+        android:theme="@style/AppTheme">
+        <activity android:name=".MainActivity">
+            <intent-filter>
+                <action android:name="android.intent.action.MAIN" />
+
+                <category android:name="android.intent.category.LAUNCHER" />
+            </intent-filter>
+        </activity>
+    </application>
+</manifest>
diff --git a/input/keyboard/CommitContentSampleApp/app/src/main/java/com/example/android/commitcontent/app/MainActivity.java b/input/keyboard/CommitContentSampleApp/app/src/main/java/com/example/android/commitcontent/app/MainActivity.java
new file mode 100644
index 0000000..72a5378
--- /dev/null
+++ b/input/keyboard/CommitContentSampleApp/app/src/main/java/com/example/android/commitcontent/app/MainActivity.java
@@ -0,0 +1,250 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License
+ */
+
+package com.example.android.commitcontent.app;
+
+import android.support.v13.view.inputmethod.EditorInfoCompat;
+import android.support.v13.view.inputmethod.InputConnectionCompat;
+import android.support.v13.view.inputmethod.InputContentInfoCompat;
+
+import android.app.Activity;
+import android.graphics.Color;
+import android.net.Uri;
+import android.os.Bundle;
+import android.os.Parcelable;
+import android.text.TextUtils;
+import android.util.Log;
+import android.view.inputmethod.EditorInfo;
+import android.view.inputmethod.InputConnection;
+import android.webkit.WebView;
+import android.widget.EditText;
+import android.widget.LinearLayout;
+import android.widget.TextView;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+
+public class MainActivity extends Activity {
+    private static final String INPUT_CONTENT_INFO_KEY = "COMMIT_CONTENT_INPUT_CONTENT_INFO";
+    private static final String COMMIT_CONTENT_FLAGS_KEY = "COMMIT_CONTENT_FLAGS";
+
+    private static String TAG = "CommitContentSupport";
+
+    private WebView mWebView;
+    private TextView mLabel;
+    private TextView mContentUri;
+    private TextView mLinkUri;
+    private TextView mMimeTypes;
+    private TextView mFlags;
+
+    private InputContentInfoCompat mCurrentInputContentInfo;
+    private int mCurrentFlags;
+
+    @Override
+    public void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+
+        setContentView(R.layout.commit_content);
+
+        final LinearLayout layout =
+                (LinearLayout) findViewById(R.id.commit_content_sample_edit_boxes);
+
+        // This declares that the IME cannot commit any content with
+        // InputConnectionCompat#commitContent().
+        layout.addView(createEditTextWithContentMimeTypes(null));
+
+        // This declares that the IME can commit contents with
+        // InputConnectionCompat#commitContent() if they match "image/gif".
+        layout.addView(createEditTextWithContentMimeTypes(new String[]{"image/gif"}));
+
+        // This declares that the IME can commit contents with
+        // InputConnectionCompat#commitContent() if they match "image/png".
+        layout.addView(createEditTextWithContentMimeTypes(new String[]{"image/png"}));
+
+        // This declares that the IME can commit contents with
+        // InputConnectionCompat#commitContent() if they match "image/jpeg".
+        layout.addView(createEditTextWithContentMimeTypes(new String[]{"image/jpeg"}));
+
+        // This declares that the IME can commit contents with
+        // InputConnectionCompat#commitContent() if they match "image/webp".
+        layout.addView(createEditTextWithContentMimeTypes(new String[]{"image/webp"}));
+
+        // This declares that the IME can commit contents with
+        // InputConnectionCompat#commitContent() if they match "image/png", "image/gif",
+        // "image/jpeg", or "image/webp".
+        layout.addView(createEditTextWithContentMimeTypes(
+                new String[]{"image/png", "image/gif", "image/jpeg", "image/webp"}));
+
+        mWebView = (WebView) findViewById(R.id.commit_content_webview);
+        mMimeTypes = (TextView) findViewById(R.id.text_commit_content_mime_types);
+        mLabel = (TextView) findViewById(R.id.text_commit_content_label);
+        mContentUri = (TextView) findViewById(R.id.text_commit_content_content_uri);
+        mLinkUri = (TextView) findViewById(R.id.text_commit_content_link_uri);
+        mFlags = (TextView) findViewById(R.id.text_commit_content_link_flags);
+
+        if (savedInstanceState != null) {
+            final InputContentInfoCompat previousInputContentInfo = InputContentInfoCompat.wrap(
+                    savedInstanceState.getParcelable(INPUT_CONTENT_INFO_KEY));
+            final int previousFlags = savedInstanceState.getInt(COMMIT_CONTENT_FLAGS_KEY);
+            if (previousInputContentInfo != null) {
+                onCommitContentInternal(previousInputContentInfo, previousFlags);
+            }
+        }
+    }
+
+    private boolean onCommitContent(InputContentInfoCompat inputContentInfo, int flags,
+            Bundle opts, String[] contentMimeTypes) {
+        // Clear the temporary permission (if any).  See below about why we do this here.
+        try {
+            if (mCurrentInputContentInfo != null) {
+                mCurrentInputContentInfo.releasePermission();
+            }
+        } catch (Exception e) {
+            Log.e(TAG, "InputContentInfoCompat#releasePermission() failed.", e);
+        } finally {
+            mCurrentInputContentInfo = null;
+        }
+
+        mWebView.loadUrl("about:blank");
+        mMimeTypes.setText("");
+        mContentUri.setText("");
+        mLabel.setText("");
+        mLinkUri.setText("");
+        mFlags.setText("");
+
+        boolean supported = false;
+        for (final String mimeType : contentMimeTypes) {
+            if (inputContentInfo.getDescription().hasMimeType(mimeType)) {
+                supported = true;
+                break;
+            }
+        }
+        if (!supported) {
+            return false;
+        }
+
+        return onCommitContentInternal(inputContentInfo, flags);
+    }
+
+    private boolean onCommitContentInternal(InputContentInfoCompat inputContentInfo, int flags) {
+        if ((flags & InputConnectionCompat.INPUT_CONTENT_GRANT_READ_URI_PERMISSION) != 0) {
+            try {
+                inputContentInfo.requestPermission();
+            } catch (Exception e) {
+                Log.e(TAG, "InputContentInfoCompat#requestPermission() failed.", e);
+                return false;
+            }
+        }
+
+        mMimeTypes.setText(
+                Arrays.toString(inputContentInfo.getDescription().filterMimeTypes("*/*")));
+        mContentUri.setText(inputContentInfo.getContentUri().toString());
+        mLabel.setText(inputContentInfo.getDescription().getLabel());
+        Uri linkUri = inputContentInfo.getLinkUri();
+        mLinkUri.setText(linkUri != null ? linkUri.toString() : "null");
+        mFlags.setText(flagsToString(flags));
+        mWebView.loadUrl(inputContentInfo.getContentUri().toString());
+        mWebView.setBackgroundColor(Color.TRANSPARENT);
+
+        // Due to the asynchronous nature of WebView, it is a bit too early to call
+        // inputContentInfo.releasePermission() here. Hence we call IC#releasePermission() when this
+        // method is called next time.  Note that calling IC#releasePermission() is just to be a
+        // good citizen. Even if we failed to call that method, the system would eventually revoke
+        // the permission sometime after inputContentInfo object gets garbage-collected.
+        mCurrentInputContentInfo = inputContentInfo;
+        mCurrentFlags = flags;
+
+        return true;
+    }
+
+    @Override
+    public void onSaveInstanceState(Bundle savedInstanceState) {
+        if (mCurrentInputContentInfo != null) {
+            savedInstanceState.putParcelable(INPUT_CONTENT_INFO_KEY,
+                    (Parcelable) mCurrentInputContentInfo.unwrap());
+            savedInstanceState.putInt(COMMIT_CONTENT_FLAGS_KEY, mCurrentFlags);
+        }
+        mCurrentInputContentInfo = null;
+        mCurrentFlags = 0;
+        super.onSaveInstanceState(savedInstanceState);
+    }
+
+    /**
+     * Creates a new instance of {@link EditText} that is configured to specify the given content
+     * MIME types to EditorInfo#contentMimeTypes so that developers can locally test how the current
+     * input method behaves for such content MIME types.
+     *
+     * @param contentMimeTypes A {@link String} array that indicates the supported content MIME
+     *                         types
+     * @return a new instance of {@link EditText}, which specifies EditorInfo#contentMimeTypes with
+     * the given content MIME types
+     */
+    private EditText createEditTextWithContentMimeTypes(String[] contentMimeTypes) {
+        final CharSequence hintText;
+        final String[] mimeTypes;  // our own copy of contentMimeTypes.
+        if (contentMimeTypes == null || contentMimeTypes.length == 0) {
+            hintText = "MIME: []";
+            mimeTypes = new String[0];
+        } else {
+            hintText = "MIME: " + Arrays.toString(contentMimeTypes);
+            mimeTypes = Arrays.copyOf(contentMimeTypes, contentMimeTypes.length);
+        }
+        EditText exitText = new EditText(this) {
+            @Override
+            public InputConnection onCreateInputConnection(EditorInfo editorInfo) {
+                final InputConnection ic = super.onCreateInputConnection(editorInfo);
+                EditorInfoCompat.setContentMimeTypes(editorInfo, mimeTypes);
+                final InputConnectionCompat.OnCommitContentListener callback =
+                        new InputConnectionCompat.OnCommitContentListener() {
+                            @Override
+                            public boolean onCommitContent(InputContentInfoCompat inputContentInfo,
+                                    int flags, Bundle opts) {
+                                return MainActivity.this.onCommitContent(
+                                        inputContentInfo, flags, opts, mimeTypes);
+                            }
+                        };
+                return InputConnectionCompat.createWrapper(ic, editorInfo, callback);
+            }
+        };
+        exitText.setHint(hintText);
+        exitText.setTextColor(Color.WHITE);
+        exitText.setHintTextColor(Color.WHITE);
+        return exitText;
+    }
+
+    /**
+     * Converts {@code flags} specified in {@link InputConnectionCompat#commitContent(
+     * InputConnection, EditorInfo, InputContentInfoCompat, int, Bundle)} to a human readable
+     * string.
+     *
+     * @param flags the 2nd parameter of
+     *              {@link InputConnectionCompat#commitContent(InputConnection, EditorInfo,
+     *              InputContentInfoCompat, int, Bundle)}
+     * @return a human readable string that corresponds to the given {@code flags}
+     */
+    private static String flagsToString(int flags) {
+        final ArrayList<String> tokens = new ArrayList<>();
+        if ((flags & InputConnectionCompat.INPUT_CONTENT_GRANT_READ_URI_PERMISSION) != 0) {
+            tokens.add("INPUT_CONTENT_GRANT_READ_URI_PERMISSION");
+            flags &= ~InputConnectionCompat.INPUT_CONTENT_GRANT_READ_URI_PERMISSION;
+        }
+        if (flags != 0) {
+            tokens.add("0x" + Integer.toHexString(flags));
+        }
+        return TextUtils.join(" | ", tokens);
+    }
+
+}
diff --git a/input/keyboard/CommitContentSampleApp/app/src/main/res/layout/commit_content.xml b/input/keyboard/CommitContentSampleApp/app/src/main/res/layout/commit_content.xml
new file mode 100644
index 0000000..414df18
--- /dev/null
+++ b/input/keyboard/CommitContentSampleApp/app/src/main/res/layout/commit_content.xml
@@ -0,0 +1,142 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2016 The Android Open Source Project
+
+     Licensed under the Apache License, Version 2.0 (the "License");
+     you may not use this file except in compliance with the License.
+     You may obtain a copy of the License at
+
+          http://www.apache.org/licenses/LICENSE-2.0
+
+     Unless required by applicable law or agreed to in writing, software
+     distributed under the License is distributed on an "AS IS" BASIS,
+     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+     See the License for the specific language governing permissions and
+     limitations under the License.
+-->
+
+<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
+             android:layout_width="match_parent"
+             android:layout_height="match_parent"
+             android:background="@android:color/black">
+
+    <WebView
+        android:id="@+id/commit_content_webview"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:background="@android:color/transparent" />
+
+    <LinearLayout
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        android:background="#77000000"
+        android:orientation="vertical">
+
+        <HorizontalScrollView
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:fadeScrollbars="false"
+            android:scrollbars="horizontal">
+
+            <TableLayout
+                android:layout_width="match_parent"
+                android:layout_height="wrap_content">
+
+                <TableRow>
+
+                    <TextView
+                        android:layout_column="1"
+                        android:gravity="end"
+                        android:padding="3dip"
+                        android:text="MIME"
+                        android:textColor="@android:color/white"
+                        android:textStyle="bold" />
+
+                    <TextView
+                        android:id="@+id/text_commit_content_mime_types"
+                        android:padding="3dip"
+                        android:textColor="@android:color/white" />
+                </TableRow>
+
+                <TableRow>
+
+                    <TextView
+                        android:layout_column="1"
+                        android:gravity="end"
+                        android:padding="3dip"
+                        android:text="Label"
+                        android:textColor="@android:color/white"
+                        android:textStyle="bold" />
+
+                    <TextView
+                        android:id="@+id/text_commit_content_label"
+                        android:padding="3dip"
+                        android:textColor="@android:color/white" />
+                </TableRow>
+
+                <TableRow>
+
+                    <TextView
+                        android:layout_column="1"
+                        android:gravity="end"
+                        android:padding="3dip"
+                        android:text="URI"
+                        android:textColor="@android:color/white"
+                        android:textStyle="bold" />
+
+                    <TextView
+                        android:id="@+id/text_commit_content_content_uri"
+                        android:padding="3dip"
+                        android:textColor="@android:color/white" />
+                </TableRow>
+
+                <TableRow>
+
+                    <TextView
+                        android:layout_column="1"
+                        android:gravity="end"
+                        android:padding="3dip"
+                        android:text="Link"
+                        android:textColor="@android:color/white"
+                        android:textStyle="bold" />
+
+                    <TextView
+                        android:id="@+id/text_commit_content_link_uri"
+                        android:padding="3dip"
+                        android:textColor="@android:color/white" />
+                </TableRow>
+
+                <TableRow>
+
+                    <TextView
+                        android:layout_column="1"
+                        android:gravity="end"
+                        android:padding="3dip"
+                        android:text="Flags"
+                        android:textColor="@android:color/white"
+                        android:textStyle="bold" />
+
+                    <TextView
+                        android:id="@+id/text_commit_content_link_flags"
+                        android:padding="3dip"
+                        android:textColor="@android:color/white" />
+                </TableRow>
+            </TableLayout>
+        </HorizontalScrollView>
+
+        <ScrollView
+            android:layout_width="match_parent"
+            android:layout_height="0dp"
+            android:layout_weight="1"
+            android:fadeScrollbars="false"
+            android:scrollbars="vertical">
+
+            <LinearLayout
+                android:id="@+id/commit_content_sample_edit_boxes"
+                android:layout_width="match_parent"
+                android:layout_height="wrap_content"
+                android:orientation="vertical" />
+        </ScrollView>
+
+    </LinearLayout>
+
+</FrameLayout>
diff --git a/input/keyboard/CommitContentSampleApp/app/src/main/res/mipmap-hdpi/ic_launcher.png b/input/keyboard/CommitContentSampleApp/app/src/main/res/mipmap-hdpi/ic_launcher.png
new file mode 100644
index 0000000..cde69bc
--- /dev/null
+++ b/input/keyboard/CommitContentSampleApp/app/src/main/res/mipmap-hdpi/ic_launcher.png
Binary files differ
diff --git a/input/keyboard/CommitContentSampleApp/app/src/main/res/mipmap-mdpi/ic_launcher.png b/input/keyboard/CommitContentSampleApp/app/src/main/res/mipmap-mdpi/ic_launcher.png
new file mode 100644
index 0000000..c133a0c
--- /dev/null
+++ b/input/keyboard/CommitContentSampleApp/app/src/main/res/mipmap-mdpi/ic_launcher.png
Binary files differ
diff --git a/input/keyboard/CommitContentSampleApp/app/src/main/res/mipmap-xhdpi/ic_launcher.png b/input/keyboard/CommitContentSampleApp/app/src/main/res/mipmap-xhdpi/ic_launcher.png
new file mode 100644
index 0000000..bfa42f0
--- /dev/null
+++ b/input/keyboard/CommitContentSampleApp/app/src/main/res/mipmap-xhdpi/ic_launcher.png
Binary files differ
diff --git a/input/keyboard/CommitContentSampleApp/app/src/main/res/mipmap-xxhdpi/ic_launcher.png b/input/keyboard/CommitContentSampleApp/app/src/main/res/mipmap-xxhdpi/ic_launcher.png
new file mode 100644
index 0000000..324e72c
--- /dev/null
+++ b/input/keyboard/CommitContentSampleApp/app/src/main/res/mipmap-xxhdpi/ic_launcher.png
Binary files differ
diff --git a/input/keyboard/CommitContentSampleApp/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png b/input/keyboard/CommitContentSampleApp/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png
new file mode 100644
index 0000000..aee44e1
--- /dev/null
+++ b/input/keyboard/CommitContentSampleApp/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png
Binary files differ
diff --git a/input/keyboard/CommitContentSampleApp/app/src/main/res/values/colors.xml b/input/keyboard/CommitContentSampleApp/app/src/main/res/values/colors.xml
new file mode 100644
index 0000000..3ab3e9c
--- /dev/null
+++ b/input/keyboard/CommitContentSampleApp/app/src/main/res/values/colors.xml
@@ -0,0 +1,6 @@
+<?xml version="1.0" encoding="utf-8"?>
+<resources>
+    <color name="colorPrimary">#3F51B5</color>
+    <color name="colorPrimaryDark">#303F9F</color>
+    <color name="colorAccent">#FF4081</color>
+</resources>
diff --git a/input/keyboard/CommitContentSampleApp/app/src/main/res/values/strings.xml b/input/keyboard/CommitContentSampleApp/app/src/main/res/values/strings.xml
new file mode 100644
index 0000000..5bcf8ea
--- /dev/null
+++ b/input/keyboard/CommitContentSampleApp/app/src/main/res/values/strings.xml
@@ -0,0 +1,3 @@
+<resources>
+    <string name="app_name">CommitContentSampleApp</string>
+</resources>
diff --git a/input/keyboard/CommitContentSampleApp/app/src/main/res/values/styles.xml b/input/keyboard/CommitContentSampleApp/app/src/main/res/values/styles.xml
new file mode 100644
index 0000000..5885930
--- /dev/null
+++ b/input/keyboard/CommitContentSampleApp/app/src/main/res/values/styles.xml
@@ -0,0 +1,11 @@
+<resources>
+
+    <!-- Base application theme. -->
+    <style name="AppTheme" parent="Theme.AppCompat.Light.DarkActionBar">
+        <!-- Customize your theme here. -->
+        <item name="colorPrimary">@color/colorPrimary</item>
+        <item name="colorPrimaryDark">@color/colorPrimaryDark</item>
+        <item name="colorAccent">@color/colorAccent</item>
+    </style>
+
+</resources>
diff --git a/input/keyboard/CommitContentSampleApp/build.gradle b/input/keyboard/CommitContentSampleApp/build.gradle
new file mode 100644
index 0000000..401add2
--- /dev/null
+++ b/input/keyboard/CommitContentSampleApp/build.gradle
@@ -0,0 +1,34 @@
+// Top-level build file where you can add configuration options common to all sub-projects/modules.
+
+buildscript {
+    repositories {
+        jcenter()
+    }
+    dependencies {
+        classpath 'com.android.tools.build:gradle:2.2.1'
+
+        // NOTE: Do not place your application dependencies here; they belong
+        // in the individual module build.gradle files
+    }
+}
+
+allprojects {
+    repositories {
+        jcenter()
+    }
+}
+
+task clean(type: Delete) {
+    delete rootProject.buildDir
+}
+
+// BEGIN_EXCLUDE
+import com.example.android.samples.build.SampleGenPlugin
+apply plugin: SampleGenPlugin
+
+samplegen {
+  pathToBuild "../../../../../build"
+  pathToSamplesCommon "../../../common"
+}
+apply from: "../../../../../build/build.gradle"
+// END_EXCLUDE
diff --git a/input/keyboard/CommitContentSampleApp/buildSrc/build.gradle b/input/keyboard/CommitContentSampleApp/buildSrc/build.gradle
new file mode 100644
index 0000000..7cebf71
--- /dev/null
+++ b/input/keyboard/CommitContentSampleApp/buildSrc/build.gradle
@@ -0,0 +1,15 @@
+repositories {
+    mavenCentral()
+}
+dependencies {
+    compile 'org.freemarker:freemarker:2.3.20'
+}
+
+sourceSets {
+    main {
+        groovy {
+            srcDir new File(rootDir, "../../../../../../build/buildSrc/src/main/groovy")
+        }
+    }
+}
+
diff --git a/input/keyboard/CommitContentSampleApp/gradle.properties b/input/keyboard/CommitContentSampleApp/gradle.properties
new file mode 100644
index 0000000..aac7c9b
--- /dev/null
+++ b/input/keyboard/CommitContentSampleApp/gradle.properties
@@ -0,0 +1,17 @@
+# Project-wide Gradle settings.
+
+# IDE (e.g. Android Studio) users:
+# Gradle settings configured through the IDE *will override*
+# any settings specified in this file.
+
+# For more details on how to configure your build environment visit
+# http://www.gradle.org/docs/current/userguide/build_environment.html
+
+# Specifies the JVM arguments used for the daemon process.
+# The setting is particularly useful for tweaking memory settings.
+org.gradle.jvmargs=-Xmx1536m
+
+# When configured, Gradle will run in incubating parallel mode.
+# This option should only be used with decoupled projects. More details, visit
+# http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects
+# org.gradle.parallel=true
diff --git a/input/keyboard/CommitContentSampleApp/gradle/wrapper/gradle-wrapper.jar b/input/keyboard/CommitContentSampleApp/gradle/wrapper/gradle-wrapper.jar
new file mode 100644
index 0000000..13372ae
--- /dev/null
+++ b/input/keyboard/CommitContentSampleApp/gradle/wrapper/gradle-wrapper.jar
Binary files differ
diff --git a/input/keyboard/CommitContentSampleApp/gradle/wrapper/gradle-wrapper.properties b/input/keyboard/CommitContentSampleApp/gradle/wrapper/gradle-wrapper.properties
new file mode 100644
index 0000000..04e285f
--- /dev/null
+++ b/input/keyboard/CommitContentSampleApp/gradle/wrapper/gradle-wrapper.properties
@@ -0,0 +1,6 @@
+#Mon Dec 28 10:00:20 PST 2015
+distributionBase=GRADLE_USER_HOME
+distributionPath=wrapper/dists
+zipStoreBase=GRADLE_USER_HOME
+zipStorePath=wrapper/dists
+distributionUrl=https\://services.gradle.org/distributions/gradle-2.14.1-all.zip
diff --git a/input/keyboard/CommitContentSampleApp/gradlew b/input/keyboard/CommitContentSampleApp/gradlew
new file mode 100755
index 0000000..9d82f78
--- /dev/null
+++ b/input/keyboard/CommitContentSampleApp/gradlew
@@ -0,0 +1,160 @@
+#!/usr/bin/env bash
+
+##############################################################################
+##
+##  Gradle start up script for UN*X
+##
+##############################################################################
+
+# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
+DEFAULT_JVM_OPTS=""
+
+APP_NAME="Gradle"
+APP_BASE_NAME=`basename "$0"`
+
+# Use the maximum available, or set MAX_FD != -1 to use that value.
+MAX_FD="maximum"
+
+warn ( ) {
+    echo "$*"
+}
+
+die ( ) {
+    echo
+    echo "$*"
+    echo
+    exit 1
+}
+
+# OS specific support (must be 'true' or 'false').
+cygwin=false
+msys=false
+darwin=false
+case "`uname`" in
+  CYGWIN* )
+    cygwin=true
+    ;;
+  Darwin* )
+    darwin=true
+    ;;
+  MINGW* )
+    msys=true
+    ;;
+esac
+
+# Attempt to set APP_HOME
+# Resolve links: $0 may be a link
+PRG="$0"
+# Need this for relative symlinks.
+while [ -h "$PRG" ] ; do
+    ls=`ls -ld "$PRG"`
+    link=`expr "$ls" : '.*-> \(.*\)$'`
+    if expr "$link" : '/.*' > /dev/null; then
+        PRG="$link"
+    else
+        PRG=`dirname "$PRG"`"/$link"
+    fi
+done
+SAVED="`pwd`"
+cd "`dirname \"$PRG\"`/" >/dev/null
+APP_HOME="`pwd -P`"
+cd "$SAVED" >/dev/null
+
+CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
+
+# Determine the Java command to use to start the JVM.
+if [ -n "$JAVA_HOME" ] ; then
+    if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
+        # IBM's JDK on AIX uses strange locations for the executables
+        JAVACMD="$JAVA_HOME/jre/sh/java"
+    else
+        JAVACMD="$JAVA_HOME/bin/java"
+    fi
+    if [ ! -x "$JAVACMD" ] ; then
+        die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
+
+Please set the JAVA_HOME variable in your environment to match the
+location of your Java installation."
+    fi
+else
+    JAVACMD="java"
+    which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
+
+Please set the JAVA_HOME variable in your environment to match the
+location of your Java installation."
+fi
+
+# Increase the maximum file descriptors if we can.
+if [ "$cygwin" = "false" -a "$darwin" = "false" ] ; then
+    MAX_FD_LIMIT=`ulimit -H -n`
+    if [ $? -eq 0 ] ; then
+        if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then
+            MAX_FD="$MAX_FD_LIMIT"
+        fi
+        ulimit -n $MAX_FD
+        if [ $? -ne 0 ] ; then
+            warn "Could not set maximum file descriptor limit: $MAX_FD"
+        fi
+    else
+        warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT"
+    fi
+fi
+
+# For Darwin, add options to specify how the application appears in the dock
+if $darwin; then
+    GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\""
+fi
+
+# For Cygwin, switch paths to Windows format before running java
+if $cygwin ; then
+    APP_HOME=`cygpath --path --mixed "$APP_HOME"`
+    CLASSPATH=`cygpath --path --mixed "$CLASSPATH"`
+    JAVACMD=`cygpath --unix "$JAVACMD"`
+
+    # We build the pattern for arguments to be converted via cygpath
+    ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null`
+    SEP=""
+    for dir in $ROOTDIRSRAW ; do
+        ROOTDIRS="$ROOTDIRS$SEP$dir"
+        SEP="|"
+    done
+    OURCYGPATTERN="(^($ROOTDIRS))"
+    # Add a user-defined pattern to the cygpath arguments
+    if [ "$GRADLE_CYGPATTERN" != "" ] ; then
+        OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)"
+    fi
+    # Now convert the arguments - kludge to limit ourselves to /bin/sh
+    i=0
+    for arg in "$@" ; do
+        CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -`
+        CHECK2=`echo "$arg"|egrep -c "^-"`                                 ### Determine if an option
+
+        if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then                    ### Added a condition
+            eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"`
+        else
+            eval `echo args$i`="\"$arg\""
+        fi
+        i=$((i+1))
+    done
+    case $i in
+        (0) set -- ;;
+        (1) set -- "$args0" ;;
+        (2) set -- "$args0" "$args1" ;;
+        (3) set -- "$args0" "$args1" "$args2" ;;
+        (4) set -- "$args0" "$args1" "$args2" "$args3" ;;
+        (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;;
+        (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;;
+        (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;;
+        (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;;
+        (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;;
+    esac
+fi
+
+# Split up the JVM_OPTS And GRADLE_OPTS values into an array, following the shell quoting and substitution rules
+function splitJvmOpts() {
+    JVM_OPTS=("$@")
+}
+eval splitJvmOpts $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS
+JVM_OPTS[${#JVM_OPTS[*]}]="-Dorg.gradle.appname=$APP_BASE_NAME"
+
+exec "$JAVACMD" "${JVM_OPTS[@]}" -classpath "$CLASSPATH" org.gradle.wrapper.GradleWrapperMain "$@"
diff --git a/input/keyboard/CommitContentSampleApp/gradlew.bat b/input/keyboard/CommitContentSampleApp/gradlew.bat
new file mode 100644
index 0000000..aec9973
--- /dev/null
+++ b/input/keyboard/CommitContentSampleApp/gradlew.bat
@@ -0,0 +1,90 @@
+@if "%DEBUG%" == "" @echo off

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

+@rem

+@rem  Gradle startup script for Windows

+@rem

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

+

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

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

+

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

+set DEFAULT_JVM_OPTS=

+

+set DIRNAME=%~dp0

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

+set APP_BASE_NAME=%~n0

+set APP_HOME=%DIRNAME%

+

+@rem Find java.exe

+if defined JAVA_HOME goto findJavaFromJavaHome

+

+set JAVA_EXE=java.exe

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

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

+

+echo.

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

+echo.

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

+echo location of your Java installation.

+

+goto fail

+

+:findJavaFromJavaHome

+set JAVA_HOME=%JAVA_HOME:"=%

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

+

+if exist "%JAVA_EXE%" goto init

+

+echo.

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

+echo.

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

+echo location of your Java installation.

+

+goto fail

+

+:init

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

+

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

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

+

+:win9xME_args

+@rem Slurp the command line arguments.

+set CMD_LINE_ARGS=

+set _SKIP=2

+

+:win9xME_args_slurp

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

+

+set CMD_LINE_ARGS=%*

+goto execute

+

+:4NT_args

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

+set CMD_LINE_ARGS=%$

+

+:execute

+@rem Setup the command line

+

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

+

+@rem Execute Gradle

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

+

+:end

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

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

+

+:fail

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

+rem the _cmd.exe /c_ return code!

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

+exit /b 1

+

+:mainEnd

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

+

+:omega

diff --git a/input/keyboard/CommitContentSampleApp/screenshots/screenshot1.png b/input/keyboard/CommitContentSampleApp/screenshots/screenshot1.png
new file mode 100644
index 0000000..0bde840
--- /dev/null
+++ b/input/keyboard/CommitContentSampleApp/screenshots/screenshot1.png
Binary files differ
diff --git a/input/keyboard/CommitContentSampleApp/settings.gradle b/input/keyboard/CommitContentSampleApp/settings.gradle
new file mode 100644
index 0000000..9d495b3
--- /dev/null
+++ b/input/keyboard/CommitContentSampleApp/settings.gradle
@@ -0,0 +1 @@
+include ':app'
\ No newline at end of file
diff --git a/input/keyboard/CommitContentSampleApp/template-params.xml b/input/keyboard/CommitContentSampleApp/template-params.xml
new file mode 100644
index 0000000..436d90a
--- /dev/null
+++ b/input/keyboard/CommitContentSampleApp/template-params.xml
@@ -0,0 +1,80 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+ Copyright 2014 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+     http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+<sample>
+    <name>CommitContentSampleApp</name>
+    <group>Input</group>
+    <package>com.example.android.commitcontent.app</package>
+
+    <minSdk>25</minSdk>
+
+    <strings>
+        <intro>
+            <![CDATA[
+                This sample demonstrates how to write an application which accepts rich content
+                (such as images) sent from a keyboard using the Commit Content API.
+            ]]>
+        </intro>
+    </strings>
+
+    <template src="base-build" />
+
+    <metadata>
+        <status>PUBLISHED</status>
+        <categories>System</categories>
+        <technologies>Android</technologies>
+        <languages>Java</languages>
+        <solutions>Mobile</solutions>
+        <level>INTERMEDIATE</level>
+        <icon>screenshots/web-icon.png</icon>
+        <screenshots>
+            <img>screenshots/screenshot-1.png</img>
+        </screenshots>
+        <api_refs>
+            <android>android.widget.EditText</android>
+            <android>import android.support.v13.view.inputmethod.EditorInfoCompat</android>
+            <android>import android.support.v13.view.inputmethod.InputConnectionCompat</android>
+            <android>import android.support.v13.view.inputmethod.InputContentInfoCompat</android>
+        </api_refs>
+
+        <description>
+<![CDATA[
+This sample demonstrates how to write an application which accepts rich content (such as images)
+sent from a keyboard using the Commit Content API.
+]]>
+        </description>
+
+        <intro>
+<![CDATA[
+Users often want to communicate with emojis, stickers, and other kinds of rich content. In previous
+versions of Android, soft keyboards (input method editors or IMEs) could send only unicode emoji to
+apps. For rich content (such as images), apps had to either build app-specific APIs that couldn't
+be used in other apps or use workarounds like sending images through the Easy Share Action or the
+clipboard.
+
+Now in Android 7.1 (API 25), the Android SDK includes the [Commit Content API][1], which provides a
+universal way for IMEs to send images and other rich content directly to a text editor in an app.
+The API is also available in the v13 Support Library (ver. 25.0), supporting devices as early as
+Android 3.2 (API 13).
+
+With this API, you can build messaging apps that accept rich content from any keyboard, as well as
+keyboards that can send rich content to any app.
+
+[1]: https://android-dot-devsite.googleplex.com/preview/image-keyboard.html
+]]>
+        </intro>
+    </metadata>
+</sample>
diff --git a/input/keyboard/CommitContentSampleIME/.gitignore b/input/keyboard/CommitContentSampleIME/.gitignore
new file mode 100644
index 0000000..39fb081
--- /dev/null
+++ b/input/keyboard/CommitContentSampleIME/.gitignore
@@ -0,0 +1,9 @@
+*.iml
+.gradle
+/local.properties
+/.idea/workspace.xml
+/.idea/libraries
+.DS_Store
+/build
+/captures
+.externalNativeBuild
diff --git a/input/keyboard/CommitContentSampleIME/app/.gitignore b/input/keyboard/CommitContentSampleIME/app/.gitignore
new file mode 100644
index 0000000..796b96d
--- /dev/null
+++ b/input/keyboard/CommitContentSampleIME/app/.gitignore
@@ -0,0 +1 @@
+/build
diff --git a/input/keyboard/CommitContentSampleIME/app/build.gradle b/input/keyboard/CommitContentSampleIME/app/build.gradle
new file mode 100644
index 0000000..0b33296
--- /dev/null
+++ b/input/keyboard/CommitContentSampleIME/app/build.gradle
@@ -0,0 +1,30 @@
+apply plugin: 'com.android.application'
+
+android {
+    compileSdkVersion 25
+    buildToolsVersion "25 rc1"
+    defaultConfig {
+        applicationId "com.example.android.supportv13.sampleime"
+        minSdkVersion 16
+        targetSdkVersion 25
+        versionCode 1
+        versionName "1.0"
+        testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
+    }
+    buildTypes {
+        release {
+            minifyEnabled false
+            proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
+        }
+    }
+}
+
+dependencies {
+    compile fileTree(include: ['*.jar'], dir: 'libs')
+    androidTestCompile('com.android.support.test.espresso:espresso-core:2.2.2', {
+        exclude group: 'com.android.support', module: 'support-annotations'
+    })
+    compile 'com.android.support:appcompat-v7:25.0.0'
+    compile 'com.android.support:support-v13:25.0.0'
+    testCompile 'junit:junit:4.12'
+}
diff --git a/input/keyboard/CommitContentSampleIME/app/proguard-rules.pro b/input/keyboard/CommitContentSampleIME/app/proguard-rules.pro
new file mode 100644
index 0000000..19b873a
--- /dev/null
+++ b/input/keyboard/CommitContentSampleIME/app/proguard-rules.pro
@@ -0,0 +1,17 @@
+# Add project specific ProGuard rules here.
+# By default, the flags in this file are appended to flags specified
+# in /usr/local/google/home/yukawa/Android/Sdk/tools/proguard/proguard-android.txt
+# You can edit the include path and order by changing the proguardFiles
+# directive in build.gradle.
+#
+# For more details, see
+#   http://developer.android.com/guide/developing/tools/proguard.html
+
+# Add any project specific keep options here:
+
+# If your project uses WebView with JS, uncomment the following
+# and specify the fully qualified class name to the JavaScript interface
+# class:
+#-keepclassmembers class fqcn.of.javascript.interface.for.webview {
+#   public *;
+#}
diff --git a/input/keyboard/CommitContentSampleIME/app/src/main/AndroidManifest.xml b/input/keyboard/CommitContentSampleIME/app/src/main/AndroidManifest.xml
new file mode 100644
index 0000000..f9891ff
--- /dev/null
+++ b/input/keyboard/CommitContentSampleIME/app/src/main/AndroidManifest.xml
@@ -0,0 +1,32 @@
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+    package="com.example.android.commitcontent.ime">
+
+    <application
+        android:allowBackup="false"
+        android:icon="@mipmap/ic_launcher"
+        android:label="@string/app_name"
+        android:supportsRtl="true"
+        android:theme="@style/AppTheme">
+
+        <service
+            android:name="com.example.android.commitcontent.ime.ImageKeyboard"
+            android:permission="android.permission.BIND_INPUT_METHOD">
+            <intent-filter>
+                <action android:name="android.view.InputMethod" />
+            </intent-filter>
+            <meta-data android:name="android.view.im" android:resource="@xml/method" />
+        </service>
+
+        <provider
+            android:name="android.support.v4.content.FileProvider"
+            android:authorities="com.example.android.commitcontent.ime.inputcontent"
+            android:exported="false"
+            android:grantUriPermissions="true">
+            <meta-data
+                android:name="android.support.FILE_PROVIDER_PATHS"
+                android:resource="@xml/file_paths" />
+        </provider>
+
+    </application>
+
+</manifest>
\ No newline at end of file
diff --git a/input/keyboard/CommitContentSampleIME/app/src/main/java/com/example/android/commitcontent/ime/ImageKeyboard.java b/input/keyboard/CommitContentSampleIME/app/src/main/java/com/example/android/commitcontent/ime/ImageKeyboard.java
new file mode 100644
index 0000000..eb5dae7
--- /dev/null
+++ b/input/keyboard/CommitContentSampleIME/app/src/main/java/com/example/android/commitcontent/ime/ImageKeyboard.java
@@ -0,0 +1,286 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.example.android.commitcontent.ime;
+
+import android.app.AppOpsManager;
+import android.content.ClipDescription;
+import android.content.Context;
+import android.content.Intent;
+import android.content.pm.PackageManager;
+import android.inputmethodservice.InputMethodService;
+import android.net.Uri;
+import android.os.Build;
+import android.support.annotation.NonNull;
+import android.support.annotation.Nullable;
+import android.support.annotation.RawRes;
+import android.support.v13.view.inputmethod.EditorInfoCompat;
+import android.support.v13.view.inputmethod.InputConnectionCompat;
+import android.support.v13.view.inputmethod.InputContentInfoCompat;
+import android.support.v4.content.FileProvider;
+import android.util.Log;
+import android.view.View;
+import android.view.inputmethod.EditorInfo;
+import android.view.inputmethod.InputBinding;
+import android.view.inputmethod.InputConnection;
+import android.widget.Button;
+import android.widget.LinearLayout;
+
+import java.io.File;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+
+
+public class ImageKeyboard extends InputMethodService {
+
+    private static final String TAG = "ImageKeyboard";
+    private static final String AUTHORITY = "com.example.android.supportv13.sampleime.inputcontent";
+    private static final String MIME_TYPE_GIF = "image/gif";
+    private static final String MIME_TYPE_PNG = "image/png";
+    private static final String MIME_TYPE_WEBP = "image/webp";
+
+    private File mPngFile;
+    private File mGifFile;
+    private File mWebpFile;
+    private Button mGifButton;
+    private Button mPngButton;
+    private Button mWebpButton;
+
+    private boolean isCommitContentSupported(
+            @Nullable EditorInfo editorInfo, @NonNull String mimeType) {
+        if (editorInfo == null) {
+            return false;
+        }
+
+        final InputConnection ic = getCurrentInputConnection();
+        if (ic == null) {
+            return false;
+        }
+
+        if (!validatePackageName(editorInfo)) {
+            return false;
+        }
+
+        final String[] supportedMimeTypes = EditorInfoCompat.getContentMimeTypes(editorInfo);
+        for (String supportedMimeType : supportedMimeTypes) {
+            if (ClipDescription.compareMimeTypes(mimeType, supportedMimeType)) {
+                return true;
+            }
+        }
+        return false;
+    }
+
+    private void doCommitContent(@NonNull String description, @NonNull String mimeType,
+            @NonNull File file) {
+        final EditorInfo editorInfo = getCurrentInputEditorInfo();
+
+        // Validate packageName again just in case.
+        if (!validatePackageName(editorInfo)) {
+            return;
+        }
+
+        final Uri contentUri = FileProvider.getUriForFile(this, AUTHORITY, file);
+
+        // As you as an IME author are most likely to have to implement your own content provider
+        // to support CommitContent API, it is important to have a clear spec about what
+        // applications are going to be allowed to access the content that your are going to share.
+        final int flag;
+        if (Build.VERSION.SDK_INT >= 25) {
+            // On API 25 and later devices, as an analogy of Intent.FLAG_GRANT_READ_URI_PERMISSION,
+            // you can specify InputConnectionCompat.INPUT_CONTENT_GRANT_READ_URI_PERMISSION to give
+            // a temporary read access to the recipient application without exporting your content
+            // provider.
+            flag = InputConnectionCompat.INPUT_CONTENT_GRANT_READ_URI_PERMISSION;
+        } else {
+            // On API 24 and prior devices, we cannot rely on
+            // InputConnectionCompat.INPUT_CONTENT_GRANT_READ_URI_PERMISSION. You as an IME author
+            // need to decide what access control is needed (or not needed) for content URIs that
+            // you are going to expose. This sample uses Context.grantUriPermission(), but you can
+            // implement your own mechanism that satisfies your own requirements.
+            flag = 0;
+            try {
+                // TODO: Use revokeUriPermission to revoke as needed.
+                grantUriPermission(
+                        editorInfo.packageName, contentUri, Intent.FLAG_GRANT_READ_URI_PERMISSION);
+            } catch (Exception e){
+                Log.e(TAG, "grantUriPermission failed packageName=" + editorInfo.packageName
+                        + " contentUri=" + contentUri, e);
+            }
+        }
+
+        final InputContentInfoCompat inputContentInfoCompat = new InputContentInfoCompat(
+                contentUri,
+                new ClipDescription(description, new String[]{mimeType}),
+                null /* linkUrl */);
+        InputConnectionCompat.commitContent(
+                getCurrentInputConnection(), getCurrentInputEditorInfo(), inputContentInfoCompat,
+                flag, null);
+    }
+
+    private boolean validatePackageName(@Nullable EditorInfo editorInfo) {
+        if (editorInfo == null) {
+            return false;
+        }
+        final String packageName = editorInfo.packageName;
+        if (packageName == null) {
+            return false;
+        }
+
+        // In Android L MR-1 and prior devices, EditorInfo.packageName is not a reliable identifier
+        // of the target application because:
+        //   1. the system does not verify it [1]
+        //   2. InputMethodManager.startInputInner() had filled EditorInfo.packageName with
+        //      view.getContext().getPackageName() [2]
+        // [1]: https://android.googlesource.com/platform/frameworks/base/+/a0f3ad1b5aabe04d9eb1df8bad34124b826ab641
+        // [2]: https://android.googlesource.com/platform/frameworks/base/+/02df328f0cd12f2af87ca96ecf5819c8a3470dc8
+        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
+            return true;
+        }
+
+        final InputBinding inputBinding = getCurrentInputBinding();
+        if (inputBinding == null) {
+            // Due to b.android.com/225029, it is possible that getCurrentInputBinding() returns
+            // null even after onStartInputView() is called.
+            // TODO: Come up with a way to work around this bug....
+            Log.e(TAG, "inputBinding should not be null here. "
+                    + "You are likely to be hitting b.android.com/225029");
+            return false;
+        }
+        final int packageUid = inputBinding.getUid();
+
+        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
+            final AppOpsManager appOpsManager =
+                    (AppOpsManager) getSystemService(Context.APP_OPS_SERVICE);
+            try {
+                appOpsManager.checkPackage(packageUid, packageName);
+            } catch (Exception e) {
+                return false;
+            }
+            return true;
+        }
+
+        final PackageManager packageManager = getPackageManager();
+        final String possiblePackageNames[] = packageManager.getPackagesForUid(packageUid);
+        for (final String possiblePackageName : possiblePackageNames) {
+            if (packageName.equals(possiblePackageName)) {
+                return true;
+            }
+        }
+        return false;
+    }
+
+    @Override
+    public void onCreate() {
+        super.onCreate();
+
+        // TODO: Avoid file I/O in the main thread.
+        final File imagesDir = new File(getFilesDir(), "images");
+        imagesDir.mkdirs();
+        mGifFile = getFileForResource(this, R.raw.animated_gif, imagesDir, "image.gif");
+        mPngFile = getFileForResource(this, R.raw.dessert_android, imagesDir, "image.png");
+        mWebpFile = getFileForResource(this, R.raw.animated_webp, imagesDir, "image.webp");
+    }
+
+    @Override
+    public View onCreateInputView() {
+        mGifButton = new Button(this);
+        mGifButton.setText("Insert GIF");
+        mGifButton.setOnClickListener(new View.OnClickListener() {
+            @Override
+            public void onClick(View view) {
+                ImageKeyboard.this.doCommitContent("A waving flag", MIME_TYPE_GIF, mGifFile);
+            }
+        });
+
+        mPngButton = new Button(this);
+        mPngButton.setText("Insert PNG");
+        mPngButton.setOnClickListener(new View.OnClickListener() {
+            @Override
+            public void onClick(View view) {
+                ImageKeyboard.this.doCommitContent("A droid logo", MIME_TYPE_PNG, mPngFile);
+            }
+        });
+
+        mWebpButton = new Button(this);
+        mWebpButton.setText("Insert WebP");
+        mWebpButton.setOnClickListener(new View.OnClickListener() {
+            @Override
+            public void onClick(View view) {
+                ImageKeyboard.this.doCommitContent(
+                        "Android N recovery animation", MIME_TYPE_WEBP, mWebpFile);
+            }
+        });
+
+        final LinearLayout layout = new LinearLayout(this);
+        layout.setOrientation(LinearLayout.VERTICAL);
+        layout.addView(mGifButton);
+        layout.addView(mPngButton);
+        layout.addView(mWebpButton);
+        return layout;
+    }
+
+    @Override
+    public boolean onEvaluateFullscreenMode() {
+        // In full-screen mode the inserted content is likely to be hidden by the IME. Hence in this
+        // sample we simply disable full-screen mode.
+        return false;
+    }
+
+    @Override
+    public void onStartInputView(EditorInfo info, boolean restarting) {
+        mGifButton.setEnabled(mGifFile != null && isCommitContentSupported(info, MIME_TYPE_GIF));
+        mPngButton.setEnabled(mPngFile != null && isCommitContentSupported(info, MIME_TYPE_PNG));
+        mWebpButton.setEnabled(mWebpFile != null && isCommitContentSupported(info, MIME_TYPE_WEBP));
+    }
+
+    private static File getFileForResource(
+            @NonNull Context context, @RawRes int res, @NonNull File outputDir,
+            @NonNull String filename) {
+        final File outputFile = new File(outputDir, filename);
+        final byte[] buffer = new byte[4096];
+        InputStream resourceReader = null;
+        try {
+            try {
+                resourceReader = context.getResources().openRawResource(res);
+                OutputStream dataWriter = null;
+                try {
+                    dataWriter = new FileOutputStream(outputFile);
+                    while (true) {
+                        final int numRead = resourceReader.read(buffer);
+                        if (numRead <= 0) {
+                            break;
+                        }
+                        dataWriter.write(buffer, 0, numRead);
+                    }
+                    return outputFile;
+                } finally {
+                    if (dataWriter != null) {
+                        dataWriter.flush();
+                        dataWriter.close();
+                    }
+                }
+            } finally {
+                if (resourceReader != null) {
+                    resourceReader.close();
+                }
+            }
+        } catch (IOException e) {
+            return null;
+        }
+    }
+}
diff --git a/input/keyboard/CommitContentSampleIME/app/src/main/res/mipmap-hdpi/ic_launcher.png b/input/keyboard/CommitContentSampleIME/app/src/main/res/mipmap-hdpi/ic_launcher.png
new file mode 100644
index 0000000..cde69bc
--- /dev/null
+++ b/input/keyboard/CommitContentSampleIME/app/src/main/res/mipmap-hdpi/ic_launcher.png
Binary files differ
diff --git a/input/keyboard/CommitContentSampleIME/app/src/main/res/mipmap-mdpi/ic_launcher.png b/input/keyboard/CommitContentSampleIME/app/src/main/res/mipmap-mdpi/ic_launcher.png
new file mode 100644
index 0000000..c133a0c
--- /dev/null
+++ b/input/keyboard/CommitContentSampleIME/app/src/main/res/mipmap-mdpi/ic_launcher.png
Binary files differ
diff --git a/input/keyboard/CommitContentSampleIME/app/src/main/res/mipmap-xhdpi/ic_launcher.png b/input/keyboard/CommitContentSampleIME/app/src/main/res/mipmap-xhdpi/ic_launcher.png
new file mode 100644
index 0000000..bfa42f0
--- /dev/null
+++ b/input/keyboard/CommitContentSampleIME/app/src/main/res/mipmap-xhdpi/ic_launcher.png
Binary files differ
diff --git a/input/keyboard/CommitContentSampleIME/app/src/main/res/mipmap-xxhdpi/ic_launcher.png b/input/keyboard/CommitContentSampleIME/app/src/main/res/mipmap-xxhdpi/ic_launcher.png
new file mode 100644
index 0000000..324e72c
--- /dev/null
+++ b/input/keyboard/CommitContentSampleIME/app/src/main/res/mipmap-xxhdpi/ic_launcher.png
Binary files differ
diff --git a/input/keyboard/CommitContentSampleIME/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png b/input/keyboard/CommitContentSampleIME/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png
new file mode 100644
index 0000000..aee44e1
--- /dev/null
+++ b/input/keyboard/CommitContentSampleIME/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png
Binary files differ
diff --git a/input/keyboard/CommitContentSampleIME/app/src/main/res/raw/animated_gif.gif b/input/keyboard/CommitContentSampleIME/app/src/main/res/raw/animated_gif.gif
new file mode 100644
index 0000000..51baf15
--- /dev/null
+++ b/input/keyboard/CommitContentSampleIME/app/src/main/res/raw/animated_gif.gif
Binary files differ
diff --git a/input/keyboard/CommitContentSampleIME/app/src/main/res/raw/animated_webp.webp b/input/keyboard/CommitContentSampleIME/app/src/main/res/raw/animated_webp.webp
new file mode 100644
index 0000000..d753a1b
--- /dev/null
+++ b/input/keyboard/CommitContentSampleIME/app/src/main/res/raw/animated_webp.webp
Binary files differ
diff --git a/input/keyboard/CommitContentSampleIME/app/src/main/res/raw/dessert_android.png b/input/keyboard/CommitContentSampleIME/app/src/main/res/raw/dessert_android.png
new file mode 100644
index 0000000..2b47c19
--- /dev/null
+++ b/input/keyboard/CommitContentSampleIME/app/src/main/res/raw/dessert_android.png
Binary files differ
diff --git a/input/keyboard/CommitContentSampleIME/app/src/main/res/values/colors.xml b/input/keyboard/CommitContentSampleIME/app/src/main/res/values/colors.xml
new file mode 100644
index 0000000..576656b
--- /dev/null
+++ b/input/keyboard/CommitContentSampleIME/app/src/main/res/values/colors.xml
@@ -0,0 +1,23 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+/**
+ * Copyright (c) 2016, The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+-->
+<resources>
+    <color name="colorPrimary">#3F51B5</color>
+    <color name="colorPrimaryDark">#303F9F</color>
+    <color name="colorAccent">#FF4081</color>
+</resources>
diff --git a/input/keyboard/CommitContentSampleIME/app/src/main/res/values/strings.xml b/input/keyboard/CommitContentSampleIME/app/src/main/res/values/strings.xml
new file mode 100644
index 0000000..952f873
--- /dev/null
+++ b/input/keyboard/CommitContentSampleIME/app/src/main/res/values/strings.xml
@@ -0,0 +1,21 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+/**
+ * Copyright (c) 2016, The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+-->
+<resources>
+    <string name="app_name">CommitContentSampleIme</string>
+</resources>
diff --git a/input/keyboard/CommitContentSampleIME/app/src/main/res/values/styles.xml b/input/keyboard/CommitContentSampleIME/app/src/main/res/values/styles.xml
new file mode 100644
index 0000000..4655b40
--- /dev/null
+++ b/input/keyboard/CommitContentSampleIME/app/src/main/res/values/styles.xml
@@ -0,0 +1,30 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+/**
+ * Copyright (c) 2016, The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+-->
+
+<resources>
+
+    <!-- Base application theme. -->
+    <style name="AppTheme" parent="Theme.AppCompat.Light.DarkActionBar">
+        <!-- Customize your theme here. -->
+        <item name="colorPrimary">@color/colorPrimary</item>
+        <item name="colorPrimaryDark">@color/colorPrimaryDark</item>
+        <item name="colorAccent">@color/colorAccent</item>
+    </style>
+
+</resources>
diff --git a/input/keyboard/CommitContentSampleIME/app/src/main/res/xml/file_paths.xml b/input/keyboard/CommitContentSampleIME/app/src/main/res/xml/file_paths.xml
new file mode 100644
index 0000000..63ac52a
--- /dev/null
+++ b/input/keyboard/CommitContentSampleIME/app/src/main/res/xml/file_paths.xml
@@ -0,0 +1,21 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+/**
+ * Copyright (c) 2016, The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+-->
+<paths xmlns:android="http://schemas.android.com/apk/res/android">
+    <files-path name="my_images" path="images/"/>
+</paths>
diff --git a/input/keyboard/CommitContentSampleIME/app/src/main/res/xml/method.xml b/input/keyboard/CommitContentSampleIME/app/src/main/res/xml/method.xml
new file mode 100644
index 0000000..defe3e3
--- /dev/null
+++ b/input/keyboard/CommitContentSampleIME/app/src/main/res/xml/method.xml
@@ -0,0 +1,19 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+/**
+ * Copyright (c) 2016, The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+-->
+<input-method xmlns:android="http://schemas.android.com/apk/res/android" />
diff --git a/input/keyboard/CommitContentSampleIME/build.gradle b/input/keyboard/CommitContentSampleIME/build.gradle
new file mode 100644
index 0000000..401add2
--- /dev/null
+++ b/input/keyboard/CommitContentSampleIME/build.gradle
@@ -0,0 +1,34 @@
+// Top-level build file where you can add configuration options common to all sub-projects/modules.
+
+buildscript {
+    repositories {
+        jcenter()
+    }
+    dependencies {
+        classpath 'com.android.tools.build:gradle:2.2.1'
+
+        // NOTE: Do not place your application dependencies here; they belong
+        // in the individual module build.gradle files
+    }
+}
+
+allprojects {
+    repositories {
+        jcenter()
+    }
+}
+
+task clean(type: Delete) {
+    delete rootProject.buildDir
+}
+
+// BEGIN_EXCLUDE
+import com.example.android.samples.build.SampleGenPlugin
+apply plugin: SampleGenPlugin
+
+samplegen {
+  pathToBuild "../../../../../build"
+  pathToSamplesCommon "../../../common"
+}
+apply from: "../../../../../build/build.gradle"
+// END_EXCLUDE
diff --git a/input/keyboard/CommitContentSampleIME/buildSrc/build.gradle b/input/keyboard/CommitContentSampleIME/buildSrc/build.gradle
new file mode 100644
index 0000000..7cebf71
--- /dev/null
+++ b/input/keyboard/CommitContentSampleIME/buildSrc/build.gradle
@@ -0,0 +1,15 @@
+repositories {
+    mavenCentral()
+}
+dependencies {
+    compile 'org.freemarker:freemarker:2.3.20'
+}
+
+sourceSets {
+    main {
+        groovy {
+            srcDir new File(rootDir, "../../../../../../build/buildSrc/src/main/groovy")
+        }
+    }
+}
+
diff --git a/input/keyboard/CommitContentSampleIME/gradle.properties b/input/keyboard/CommitContentSampleIME/gradle.properties
new file mode 100644
index 0000000..aac7c9b
--- /dev/null
+++ b/input/keyboard/CommitContentSampleIME/gradle.properties
@@ -0,0 +1,17 @@
+# Project-wide Gradle settings.
+
+# IDE (e.g. Android Studio) users:
+# Gradle settings configured through the IDE *will override*
+# any settings specified in this file.
+
+# For more details on how to configure your build environment visit
+# http://www.gradle.org/docs/current/userguide/build_environment.html
+
+# Specifies the JVM arguments used for the daemon process.
+# The setting is particularly useful for tweaking memory settings.
+org.gradle.jvmargs=-Xmx1536m
+
+# When configured, Gradle will run in incubating parallel mode.
+# This option should only be used with decoupled projects. More details, visit
+# http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects
+# org.gradle.parallel=true
diff --git a/input/keyboard/CommitContentSampleIME/gradle/wrapper/gradle-wrapper.jar b/input/keyboard/CommitContentSampleIME/gradle/wrapper/gradle-wrapper.jar
new file mode 100644
index 0000000..13372ae
--- /dev/null
+++ b/input/keyboard/CommitContentSampleIME/gradle/wrapper/gradle-wrapper.jar
Binary files differ
diff --git a/input/keyboard/CommitContentSampleIME/gradle/wrapper/gradle-wrapper.properties b/input/keyboard/CommitContentSampleIME/gradle/wrapper/gradle-wrapper.properties
new file mode 100644
index 0000000..04e285f
--- /dev/null
+++ b/input/keyboard/CommitContentSampleIME/gradle/wrapper/gradle-wrapper.properties
@@ -0,0 +1,6 @@
+#Mon Dec 28 10:00:20 PST 2015
+distributionBase=GRADLE_USER_HOME
+distributionPath=wrapper/dists
+zipStoreBase=GRADLE_USER_HOME
+zipStorePath=wrapper/dists
+distributionUrl=https\://services.gradle.org/distributions/gradle-2.14.1-all.zip
diff --git a/input/keyboard/CommitContentSampleIME/gradlew b/input/keyboard/CommitContentSampleIME/gradlew
new file mode 100755
index 0000000..9d82f78
--- /dev/null
+++ b/input/keyboard/CommitContentSampleIME/gradlew
@@ -0,0 +1,160 @@
+#!/usr/bin/env bash
+
+##############################################################################
+##
+##  Gradle start up script for UN*X
+##
+##############################################################################
+
+# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
+DEFAULT_JVM_OPTS=""
+
+APP_NAME="Gradle"
+APP_BASE_NAME=`basename "$0"`
+
+# Use the maximum available, or set MAX_FD != -1 to use that value.
+MAX_FD="maximum"
+
+warn ( ) {
+    echo "$*"
+}
+
+die ( ) {
+    echo
+    echo "$*"
+    echo
+    exit 1
+}
+
+# OS specific support (must be 'true' or 'false').
+cygwin=false
+msys=false
+darwin=false
+case "`uname`" in
+  CYGWIN* )
+    cygwin=true
+    ;;
+  Darwin* )
+    darwin=true
+    ;;
+  MINGW* )
+    msys=true
+    ;;
+esac
+
+# Attempt to set APP_HOME
+# Resolve links: $0 may be a link
+PRG="$0"
+# Need this for relative symlinks.
+while [ -h "$PRG" ] ; do
+    ls=`ls -ld "$PRG"`
+    link=`expr "$ls" : '.*-> \(.*\)$'`
+    if expr "$link" : '/.*' > /dev/null; then
+        PRG="$link"
+    else
+        PRG=`dirname "$PRG"`"/$link"
+    fi
+done
+SAVED="`pwd`"
+cd "`dirname \"$PRG\"`/" >/dev/null
+APP_HOME="`pwd -P`"
+cd "$SAVED" >/dev/null
+
+CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
+
+# Determine the Java command to use to start the JVM.
+if [ -n "$JAVA_HOME" ] ; then
+    if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
+        # IBM's JDK on AIX uses strange locations for the executables
+        JAVACMD="$JAVA_HOME/jre/sh/java"
+    else
+        JAVACMD="$JAVA_HOME/bin/java"
+    fi
+    if [ ! -x "$JAVACMD" ] ; then
+        die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
+
+Please set the JAVA_HOME variable in your environment to match the
+location of your Java installation."
+    fi
+else
+    JAVACMD="java"
+    which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
+
+Please set the JAVA_HOME variable in your environment to match the
+location of your Java installation."
+fi
+
+# Increase the maximum file descriptors if we can.
+if [ "$cygwin" = "false" -a "$darwin" = "false" ] ; then
+    MAX_FD_LIMIT=`ulimit -H -n`
+    if [ $? -eq 0 ] ; then
+        if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then
+            MAX_FD="$MAX_FD_LIMIT"
+        fi
+        ulimit -n $MAX_FD
+        if [ $? -ne 0 ] ; then
+            warn "Could not set maximum file descriptor limit: $MAX_FD"
+        fi
+    else
+        warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT"
+    fi
+fi
+
+# For Darwin, add options to specify how the application appears in the dock
+if $darwin; then
+    GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\""
+fi
+
+# For Cygwin, switch paths to Windows format before running java
+if $cygwin ; then
+    APP_HOME=`cygpath --path --mixed "$APP_HOME"`
+    CLASSPATH=`cygpath --path --mixed "$CLASSPATH"`
+    JAVACMD=`cygpath --unix "$JAVACMD"`
+
+    # We build the pattern for arguments to be converted via cygpath
+    ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null`
+    SEP=""
+    for dir in $ROOTDIRSRAW ; do
+        ROOTDIRS="$ROOTDIRS$SEP$dir"
+        SEP="|"
+    done
+    OURCYGPATTERN="(^($ROOTDIRS))"
+    # Add a user-defined pattern to the cygpath arguments
+    if [ "$GRADLE_CYGPATTERN" != "" ] ; then
+        OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)"
+    fi
+    # Now convert the arguments - kludge to limit ourselves to /bin/sh
+    i=0
+    for arg in "$@" ; do
+        CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -`
+        CHECK2=`echo "$arg"|egrep -c "^-"`                                 ### Determine if an option
+
+        if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then                    ### Added a condition
+            eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"`
+        else
+            eval `echo args$i`="\"$arg\""
+        fi
+        i=$((i+1))
+    done
+    case $i in
+        (0) set -- ;;
+        (1) set -- "$args0" ;;
+        (2) set -- "$args0" "$args1" ;;
+        (3) set -- "$args0" "$args1" "$args2" ;;
+        (4) set -- "$args0" "$args1" "$args2" "$args3" ;;
+        (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;;
+        (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;;
+        (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;;
+        (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;;
+        (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;;
+    esac
+fi
+
+# Split up the JVM_OPTS And GRADLE_OPTS values into an array, following the shell quoting and substitution rules
+function splitJvmOpts() {
+    JVM_OPTS=("$@")
+}
+eval splitJvmOpts $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS
+JVM_OPTS[${#JVM_OPTS[*]}]="-Dorg.gradle.appname=$APP_BASE_NAME"
+
+exec "$JAVACMD" "${JVM_OPTS[@]}" -classpath "$CLASSPATH" org.gradle.wrapper.GradleWrapperMain "$@"
diff --git a/input/keyboard/CommitContentSampleIME/gradlew.bat b/input/keyboard/CommitContentSampleIME/gradlew.bat
new file mode 100644
index 0000000..aec9973
--- /dev/null
+++ b/input/keyboard/CommitContentSampleIME/gradlew.bat
@@ -0,0 +1,90 @@
+@if "%DEBUG%" == "" @echo off

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

+@rem

+@rem  Gradle startup script for Windows

+@rem

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

+

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

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

+

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

+set DEFAULT_JVM_OPTS=

+

+set DIRNAME=%~dp0

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

+set APP_BASE_NAME=%~n0

+set APP_HOME=%DIRNAME%

+

+@rem Find java.exe

+if defined JAVA_HOME goto findJavaFromJavaHome

+

+set JAVA_EXE=java.exe

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

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

+

+echo.

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

+echo.

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

+echo location of your Java installation.

+

+goto fail

+

+:findJavaFromJavaHome

+set JAVA_HOME=%JAVA_HOME:"=%

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

+

+if exist "%JAVA_EXE%" goto init

+

+echo.

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

+echo.

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

+echo location of your Java installation.

+

+goto fail

+

+:init

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

+

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

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

+

+:win9xME_args

+@rem Slurp the command line arguments.

+set CMD_LINE_ARGS=

+set _SKIP=2

+

+:win9xME_args_slurp

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

+

+set CMD_LINE_ARGS=%*

+goto execute

+

+:4NT_args

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

+set CMD_LINE_ARGS=%$

+

+:execute

+@rem Setup the command line

+

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

+

+@rem Execute Gradle

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

+

+:end

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

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

+

+:fail

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

+rem the _cmd.exe /c_ return code!

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

+exit /b 1

+

+:mainEnd

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

+

+:omega

diff --git a/input/keyboard/CommitContentSampleIME/screenshots/screenshot1.png b/input/keyboard/CommitContentSampleIME/screenshots/screenshot1.png
new file mode 100644
index 0000000..0bde840
--- /dev/null
+++ b/input/keyboard/CommitContentSampleIME/screenshots/screenshot1.png
Binary files differ
diff --git a/input/keyboard/CommitContentSampleIME/settings.gradle b/input/keyboard/CommitContentSampleIME/settings.gradle
new file mode 100644
index 0000000..e7b4def
--- /dev/null
+++ b/input/keyboard/CommitContentSampleIME/settings.gradle
@@ -0,0 +1 @@
+include ':app'
diff --git a/input/keyboard/CommitContentSampleIME/template-params.xml b/input/keyboard/CommitContentSampleIME/template-params.xml
new file mode 100644
index 0000000..800a864
--- /dev/null
+++ b/input/keyboard/CommitContentSampleIME/template-params.xml
@@ -0,0 +1,80 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+ Copyright 2014 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+     http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+<sample>
+    <name>CommitContentSampleIME</name>
+    <group>Input</group>
+    <package>com.example.android.commitcontent.ime</package>
+
+    <minSdk>25</minSdk>
+
+    <strings>
+        <intro>
+            <![CDATA[
+                This sample demonstrates how to write an keyboard which sends rich content
+                (such as images) to text fields using the Commit Content API.
+            ]]>
+        </intro>
+    </strings>
+
+    <template src="base-build" />
+
+    <metadata>
+        <status>PUBLISHED</status>
+        <categories>System</categories>
+        <technologies>Android</technologies>
+        <languages>Java</languages>
+        <solutions>Mobile</solutions>
+        <level>INTERMEDIATE</level>
+        <icon>screenshots/web-icon.png</icon>
+        <screenshots>
+            <img>screenshots/screenshot-1.png</img>
+        </screenshots>
+        <api_refs>
+            <android>android.widget.EditText</android>
+            <android>import android.support.v13.view.inputmethod.EditorInfoCompat</android>
+            <android>import android.support.v13.view.inputmethod.InputConnectionCompat</android>
+            <android>import android.support.v13.view.inputmethod.InputContentInfoCompat</android>
+        </api_refs>
+
+        <description>
+<![CDATA[
+This sample demonstrates how to write an keyboard which sends rich content (such as images) to text
+fields using the Commit Content API.
+]]>
+        </description>
+
+        <intro>
+<![CDATA[
+Users often want to communicate with emojis, stickers, and other kinds of rich content. In previous
+versions of Android, soft keyboards (input method editors or IMEs) could send only unicode emoji to
+apps. For rich content (such as images), apps had to either build app-specific APIs that couldn't
+be used in other apps or use workarounds like sending images through the Easy Share Action or the
+clipboard.
+
+Now in Android 7.1 (API 25), the Android SDK includes the [Commit Content API][1], which provides a
+universal way for IMEs to send images and other rich content directly to a text editor in an app.
+The API is also available in the v13 Support Library (ver. 25.0), supporting devices as early as
+Android 3.2 (API 13).
+
+With this API, you can build messaging apps that accept rich content from any keyboard, as well as
+keyboards that can send rich content to any app.
+
+[1]: https://android-dot-devsite.googleplex.com/preview/image-keyboard.html
+]]>
+        </intro>
+    </metadata>
+</sample>