Merge "DO NOT MERGE - Merge Android 10 into master"
diff --git a/content/SharingShortcuts/Application/.gitignore b/content/SharingShortcuts/Application/.gitignore
new file mode 100644
index 0000000..6eb878d
--- /dev/null
+++ b/content/SharingShortcuts/Application/.gitignore
@@ -0,0 +1,16 @@
+# Copyright 2013 The Android Open Source Project
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+src/template/
+src/common/
+build.gradle
diff --git a/content/SharingShortcuts/Application/proguard-project.txt b/content/SharingShortcuts/Application/proguard-project.txt
new file mode 100644
index 0000000..f2fe155
--- /dev/null
+++ b/content/SharingShortcuts/Application/proguard-project.txt
@@ -0,0 +1,20 @@
+# To enable ProGuard in your project, edit project.properties
+# to define the proguard.config property as described in that file.
+#
+# Add project specific ProGuard rules here.
+# By default, the flags in this file are appended to flags specified
+# in ${sdk.dir}/tools/proguard/proguard-android.txt
+# You can edit the include path and order by changing the ProGuard
+# include property in project.properties.
+#
+# For more details, see
+#   http://developer.android.com/guide/developing/tools/proguard.html
+
+# Add any project specific keep options here:
+
+# If your project uses WebView with JS, uncomment the following
+# and specify the fully qualified class name to the JavaScript interface
+# class:
+#-keepclassmembers class fqcn.of.javascript.interface.for.webview {
+#   public *;
+#}
diff --git a/content/SharingShortcuts/Application/src/androidTest/java/com/example/android/sharingshortcuts/test/SampleTest.java b/content/SharingShortcuts/Application/src/androidTest/java/com/example/android/sharingshortcuts/test/SampleTest.java
new file mode 100644
index 0000000..0a5bd70
--- /dev/null
+++ b/content/SharingShortcuts/Application/src/androidTest/java/com/example/android/sharingshortcuts/test/SampleTest.java
@@ -0,0 +1,45 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR 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.sharingshortcuts;
+
+import android.test.ActivityInstrumentationTestCase2;
+
+/**
+ * Tests for DirectShare sample.
+ */
+public class SampleTest extends ActivityInstrumentationTestCase2<MainActivity> {
+
+    private MainActivity mTestActivity;
+
+    public SampleTest() {
+        super(MainActivity.class);
+    }
+
+    @Override
+    protected void setUp() throws Exception {
+        super.setUp();
+        mTestActivity = getActivity();
+    }
+
+    /**
+     * Test if the test fixture has been set up correctly.
+     */
+    public void testPreconditions() {
+        assertNotNull("mTestActivity is null", mTestActivity);
+    }
+
+}
diff --git a/content/SharingShortcuts/Application/src/main/AndroidManifest.xml b/content/SharingShortcuts/Application/src/main/AndroidManifest.xml
new file mode 100644
index 0000000..d03e7c9
--- /dev/null
+++ b/content/SharingShortcuts/Application/src/main/AndroidManifest.xml
@@ -0,0 +1,83 @@
+<?xml version="1.0" encoding="UTF-8"?><!--
+ Copyright 2019 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+     http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:tools="http://schemas.android.com/tools"
+    package="com.example.android.sharingshortcuts"
+    android:versionCode="1"
+    android:versionName="1.0">
+
+    <application
+        android:allowBackup="true"
+        android:icon="@mipmap/ic_launcher"
+        android:label="@string/app_name"
+        android:theme="@style/SharingShortcutsTheme"
+        tools:ignore="GoogleAppIndexingWarning">
+
+        <activity
+            android:name=".MainActivity"
+            android:label="@string/app_name">
+            <intent-filter>
+                <action android:name="android.intent.action.MAIN" />
+                <category android:name="android.intent.category.LAUNCHER" />
+            </intent-filter>
+            <!-- Reference resource file where the app's shortcuts are defined -->
+            <meta-data
+                android:name="android.app.shortcuts"
+                android:resource="@xml/shortcuts" />
+        </activity>
+
+        <activity
+            android:name=".SendMessageActivity"
+            android:label="@string/app_name"
+            android:theme="@style/SharingShortcutsDialogTheme">
+            <!-- This activity can respond to Intents of type SEND and with text/plain data -->
+            <intent-filter>
+                <action android:name="android.intent.action.SEND" />
+                <category android:name="android.intent.category.DEFAULT" />
+                <data android:mimeType="text/plain" />
+            </intent-filter>
+            <!-- Only needed if you import the sharetarget AndroidX library that provides
+                 backwards compatibility with the old DirectShare API.
+                 The activity that receives the Sharing Shortcut intent needs to be taken into
+                 account with this chooser target provider. -->
+            <meta-data
+                android:name="android.service.chooser.chooser_target_service"
+                android:value="androidx.sharetarget.ChooserTargetServiceCompat" />
+        </activity>
+
+        <activity
+            android:name=".SelectContactActivity"
+            android:label="@string/app_name"
+            android:theme="@style/SharingShortcutsDialogTheme" />
+
+        <!-- Only needed if you want to add a thumbnail to the direct share.
+            FileProvider is a subclass of ContentProvider that facilitates secure sharing.
+            Here we specify a FileProvider for our app. -->
+        <provider
+            android:name="androidx.core.content.FileProvider"
+            android:authorities="com.example.android.sharingshortcuts.fileprovider"
+            android:exported="false"
+            android:grantUriPermissions="true">
+            <!-- Specify the directories the FileProvider can generate content URIs for. -->
+            <meta-data
+                android:name="android.support.FILE_PROVIDER_PATHS"
+                android:resource="@xml/file_paths" />
+        </provider>
+
+    </application>
+
+</manifest>
diff --git a/content/SharingShortcuts/Application/src/main/java/com/example/android/sharingshortcuts/Contact.java b/content/SharingShortcuts/Application/src/main/java/com/example/android/sharingshortcuts/Contact.java
new file mode 100644
index 0000000..155a841
--- /dev/null
+++ b/content/SharingShortcuts/Application/src/main/java/com/example/android/sharingshortcuts/Contact.java
@@ -0,0 +1,95 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR 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.sharingshortcuts;
+
+import androidx.annotation.NonNull;
+
+/**
+ * Provides the list of dummy contacts. This sample implements this as constants, but real-life apps
+ * should use a database and such.
+ */
+public class Contact {
+
+    /**
+     * The list of dummy contacts.
+     */
+    public static final Contact[] CONTACTS = {
+            new Contact("Tereasa"),
+            new Contact("Chang"),
+            new Contact("Kory"),
+            new Contact("Clare"),
+            new Contact("Landon"),
+            new Contact("Kyle"),
+            new Contact("Deana"),
+            new Contact("Daria"),
+            new Contact("Melisa"),
+            new Contact("Sammie"),
+    };
+
+    /**
+     * The contact ID.
+     */
+    public static final String ID = "contact_id";
+
+    /**
+     * Representative invalid contact ID.
+     */
+    public static final int INVALID_ID = -1;
+
+    /**
+     * The name of this contact.
+     */
+    private final String mName;
+
+    /**
+     * Instantiates a new {@link Contact}.
+     *
+     * @param name The name of the contact.
+     */
+    public Contact(@NonNull String name) {
+        mName = name;
+    }
+
+    /**
+     * Finds a {@link Contact} specified by a contact ID.
+     *
+     * @param id The contact ID. This needs to be a valid ID.
+     * @return A {@link Contact}
+     */
+    public static Contact byId(int id) {
+        return CONTACTS[id];
+    }
+
+    /**
+     * Gets the name of this contact.
+     *
+     * @return The name of this contact.
+     */
+    public String getName() {
+        return mName;
+    }
+
+    /**
+     * Gets the icon of this contact.
+     *
+     * @return The icon.
+     */
+    public int getIcon() {
+        return R.mipmap.logo_avatar;
+    }
+
+}
diff --git a/content/SharingShortcuts/Application/src/main/java/com/example/android/sharingshortcuts/ContactViewBinder.java b/content/SharingShortcuts/Application/src/main/java/com/example/android/sharingshortcuts/ContactViewBinder.java
new file mode 100644
index 0000000..8692592
--- /dev/null
+++ b/content/SharingShortcuts/Application/src/main/java/com/example/android/sharingshortcuts/ContactViewBinder.java
@@ -0,0 +1,37 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR 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.sharingshortcuts;
+
+import android.widget.TextView;
+
+/**
+ * A simple utility to bind a {@link TextView} with a {@link Contact}.
+ */
+public class ContactViewBinder {
+
+    /**
+     * Binds the {@code textView} with the specified {@code contact}.
+     *
+     * @param contact  The contact.
+     * @param textView The TextView.
+     */
+    public static void bind(Contact contact, TextView textView) {
+        textView.setText(contact.getName());
+        textView.setCompoundDrawablesRelativeWithIntrinsicBounds(contact.getIcon(), 0, 0, 0);
+    }
+
+}
diff --git a/content/SharingShortcuts/Application/src/main/java/com/example/android/sharingshortcuts/MainActivity.java b/content/SharingShortcuts/Application/src/main/java/com/example/android/sharingshortcuts/MainActivity.java
new file mode 100644
index 0000000..17dfdc9
--- /dev/null
+++ b/content/SharingShortcuts/Application/src/main/java/com/example/android/sharingshortcuts/MainActivity.java
@@ -0,0 +1,145 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR 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.sharingshortcuts;
+
+import android.app.Activity;
+import android.content.ClipData;
+import android.content.Intent;
+import android.graphics.Bitmap;
+import android.graphics.BitmapFactory;
+import android.net.Uri;
+import android.os.Bundle;
+import android.view.View;
+import android.widget.EditText;
+
+import androidx.core.content.FileProvider;
+
+import java.io.File;
+import java.io.FileNotFoundException;
+import java.io.FileOutputStream;
+import java.io.IOException;
+
+/**
+ * Provides the landing screen of this sample. There is nothing particularly interesting here. All
+ * the codes related to the Direct Share feature are in {@link SharingShortcutsManager}.
+ */
+public class MainActivity extends Activity {
+
+    // Domain authority for our app FileProvider
+    private static final String FILE_PROVIDER_AUTHORITY =
+            "com.example.android.sharingshortcuts.fileprovider";
+
+    // Cache directory to store images
+    // This is the same path specified in the @xml/file_paths and accessed from the AndroidManifest
+    private static final String IMAGE_CACHE_DIR = "images";
+
+    // Name of the file to use for the thumbnail image
+    private static final String IMAGE_FILE = "image.png";
+
+    private EditText mEditBody;
+    private SharingShortcutsManager mSharingShortcutsManager;
+
+    @Override
+    protected void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+        setContentView(R.layout.activity_main);
+        mEditBody = findViewById(R.id.body);
+        findViewById(R.id.share).setOnClickListener(mOnClickListener);
+
+        mSharingShortcutsManager = new SharingShortcutsManager();
+        mSharingShortcutsManager.pushDirectShareTargets(this);
+    }
+
+    private View.OnClickListener mOnClickListener = new View.OnClickListener() {
+        @Override
+        public void onClick(View v) {
+            switch (v.getId()) {
+                case R.id.share:
+                    share();
+                    break;
+            }
+        }
+    };
+
+    /**
+     * Emits a sample share {@link Intent}.
+     */
+    private void share() {
+        Intent sharingIntent = new Intent(android.content.Intent.ACTION_SEND);
+        sharingIntent.setType("text/plain");
+        sharingIntent.putExtra(android.content.Intent.EXTRA_TEXT, mEditBody.getText().toString());
+        // (Optional) If you want a preview title, set it with Intent.EXTRA_TITLE
+        sharingIntent.putExtra(Intent.EXTRA_TITLE, getString(R.string.send_intent_title));
+
+        // (Optional) if you want a preview thumbnail, create a content URI and add it
+        // The system only supports content URIs
+        ClipData thumbnail = getClipDataThumbnail();
+        if (thumbnail != null) {
+            sharingIntent.setClipData(thumbnail);
+            sharingIntent.setFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
+        }
+
+        startActivity(Intent.createChooser(sharingIntent, null));
+    }
+
+    /**
+     * Get ClipData thumbnail object that needs to be passed in the Intent.
+     * It stores the launcher icon in the cache and retrieves in a content URI.
+     * The ClipData object is created with the URI we get from the FileProvider.
+     * <p>
+     * For this to work, you need to configure a FileProvider in the project. We added it to the
+     * AndroidManifest.xml file where we can configure it. We added the images path where we
+     * save the image to the @xml/file_paths file which tells the FileProvider where we intend to
+     * request content URIs.
+     *
+     * @return thumbnail ClipData object to set in the sharing Intent.
+     */
+    private ClipData getClipDataThumbnail() {
+        try {
+            Uri contentUri = saveImageThumbnail();
+            return ClipData.newUri(getContentResolver(), null, contentUri);
+        } catch (FileNotFoundException e) {
+            e.printStackTrace();
+            return null;
+        } catch (IOException e) {
+            e.printStackTrace();
+            return null;
+        }
+    }
+
+    /**
+     * Save our Launcher image to the cache and return it as a content URI.
+     *
+     * IMPORTANT: This could trigger StrictMode. Do not do this in your app.
+     * For the simplicity of the code sample, this is running on the Main thread
+     * but these tasks should be done in a background thread.
+     *
+     * @throws IOException if image couldn't be saved to the cache.
+     * @return image content Uri
+     */
+    private Uri saveImageThumbnail() throws IOException {
+        Bitmap bm = BitmapFactory.decodeResource(getResources(), R.mipmap.ic_launcher);
+        File cachePath = new File(getCacheDir(), IMAGE_CACHE_DIR);
+        cachePath.mkdirs();
+        FileOutputStream stream = new FileOutputStream(cachePath + "/" + IMAGE_FILE);
+        bm.compress(Bitmap.CompressFormat.PNG, 100, stream);
+        stream.close();
+        File imagePath = new File(getCacheDir(), IMAGE_CACHE_DIR);
+        File newFile = new File(imagePath, IMAGE_FILE);
+        return FileProvider.getUriForFile(this, FILE_PROVIDER_AUTHORITY, newFile);
+    }
+}
diff --git a/content/SharingShortcuts/Application/src/main/java/com/example/android/sharingshortcuts/SelectContactActivity.java b/content/SharingShortcuts/Application/src/main/java/com/example/android/sharingshortcuts/SelectContactActivity.java
new file mode 100644
index 0000000..4e4d543
--- /dev/null
+++ b/content/SharingShortcuts/Application/src/main/java/com/example/android/sharingshortcuts/SelectContactActivity.java
@@ -0,0 +1,98 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR 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.sharingshortcuts;
+
+import android.app.Activity;
+import android.content.Intent;
+import android.os.Bundle;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.TextView;
+
+import androidx.annotation.NonNull;
+import androidx.recyclerview.widget.LinearLayoutManager;
+import androidx.recyclerview.widget.RecyclerView;
+
+/**
+ * The dialog for selecting a contact to share the text with. This dialog is shown when the user
+ * taps on this sample's icon rather than any of the Direct Share contacts.
+ */
+public class SelectContactActivity extends Activity {
+
+    /**
+     * The action string for Intents.
+     */
+    public static final String ACTION_SELECT_CONTACT =
+            "com.example.android.sharingshortcuts.intent.action.SELECT_CONTACT";
+
+    @Override
+    protected void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+        setContentView(R.layout.activity_select_contact);
+        Intent intent = getIntent();
+        if (!ACTION_SELECT_CONTACT.equals(intent.getAction())) {
+            finish();
+            return;
+        }
+        // Set up the list of contacts
+        RecyclerView recyclerView = findViewById(R.id.recycler_view);
+        recyclerView.setAdapter(mContactAdapter);
+        recyclerView.setLayoutManager(new LinearLayoutManager(this));
+    }
+
+    private final RecyclerView.Adapter mContactAdapter =
+            new RecyclerView.Adapter<ContactViewHolder>() {
+
+                @NonNull
+                @Override
+                public ContactViewHolder onCreateViewHolder(@NonNull ViewGroup parent,
+                                                            int viewType) {
+                    TextView textView = (TextView) LayoutInflater.from(parent.getContext())
+                            .inflate(R.layout.item_contact, parent, false);
+                    return new ContactViewHolder(textView);
+                }
+
+                @Override
+                public void onBindViewHolder(@NonNull ContactViewHolder holder,
+                                             final int position) {
+                    Contact contact = Contact.CONTACTS[position];
+                    ContactViewBinder.bind(contact, (TextView) holder.itemView);
+                    holder.itemView.setOnClickListener(new View.OnClickListener() {
+                        @Override
+                        public void onClick(View view) {
+                            Intent data = new Intent();
+                            data.putExtra(Contact.ID, position);
+                            setResult(RESULT_OK, data);
+                            finish();
+                        }
+                    });
+                }
+
+                @Override
+                public int getItemCount() {
+                    return Contact.CONTACTS.length;
+                }
+            };
+
+    private static class ContactViewHolder extends RecyclerView.ViewHolder {
+
+        ContactViewHolder(@NonNull TextView textView) {
+            super(textView);
+        }
+    }
+}
diff --git a/content/SharingShortcuts/Application/src/main/java/com/example/android/sharingshortcuts/SendMessageActivity.java b/content/SharingShortcuts/Application/src/main/java/com/example/android/sharingshortcuts/SendMessageActivity.java
new file mode 100644
index 0000000..7519a7b
--- /dev/null
+++ b/content/SharingShortcuts/Application/src/main/java/com/example/android/sharingshortcuts/SendMessageActivity.java
@@ -0,0 +1,162 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR 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.sharingshortcuts;
+
+import android.app.Activity;
+import android.content.Intent;
+import android.os.Build;
+import android.os.Bundle;
+import android.view.View;
+import android.widget.TextView;
+import android.widget.Toast;
+
+/**
+ * Provides the UI for sharing a text with a {@link Contact}.
+ */
+public class SendMessageActivity extends Activity {
+
+    /**
+     * The request code for {@link SelectContactActivity}. This is used when the user doesn't
+     * select any of Direct Share icons.
+     */
+    private static final int REQUEST_SELECT_CONTACT = 1;
+
+    /**
+     * The text to share.
+     */
+    private String mBody;
+
+    /**
+     * The ID of the contact to share the text with.
+     */
+    private int mContactId;
+
+    // View references.
+    private TextView mTextContactName;
+    private TextView mTextMessageBody;
+
+    @Override
+    protected void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+        setContentView(R.layout.activity_send_message);
+        setTitle(R.string.sending_message);
+        // View references.
+        mTextContactName = findViewById(R.id.contact_name);
+        mTextMessageBody = findViewById(R.id.message_body);
+        // Handle the share Intent.
+        boolean handled = handleIntent(getIntent());
+        if (!handled) {
+            finish();
+            return;
+        }
+        // Bind event handlers.
+        findViewById(R.id.send).setOnClickListener(mOnClickListener);
+        // Set up the UI.
+        prepareUi();
+        // The contact ID will not be passed on when the user clicks on the app icon rather than any
+        // of the Direct Share icons. In this case, we show another dialog for selecting a contact.
+        if (mContactId == Contact.INVALID_ID) {
+            selectContact();
+        }
+    }
+
+    @Override
+    protected void onActivityResult(int requestCode, int resultCode, Intent data) {
+        switch (requestCode) {
+            case REQUEST_SELECT_CONTACT:
+                if (resultCode == RESULT_OK) {
+                    mContactId = data.getIntExtra(Contact.ID, Contact.INVALID_ID);
+                }
+                // Give up sharing the send_message if the user didn't choose a contact.
+                if (mContactId == Contact.INVALID_ID) {
+                    finish();
+                    return;
+                }
+                prepareUi();
+                break;
+            default:
+                super.onActivityResult(requestCode, resultCode, data);
+        }
+    }
+
+    /**
+     * Handles the passed {@link Intent}. This method can only handle intents for sharing a plain
+     * text. {@link #mBody} and {@link #mContactId} are modified accordingly.
+     *
+     * @param intent The {@link Intent}.
+     * @return true if the {@code intent} is handled properly.
+     */
+    private boolean handleIntent(Intent intent) {
+        if (Intent.ACTION_SEND.equals(intent.getAction())
+                && "text/plain".equals(intent.getType())) {
+            mBody = intent.getStringExtra(Intent.EXTRA_TEXT);
+            // The intent comes from Direct share
+            if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P
+                    && intent.hasExtra(Intent.EXTRA_SHORTCUT_ID)) {
+                String shortcutId = intent.getStringExtra(Intent.EXTRA_SHORTCUT_ID);
+                mContactId = Integer.valueOf(shortcutId);
+            } else {
+                // The text was shared and the user chose our app
+                mContactId = Contact.INVALID_ID;
+            }
+            return true;
+        }
+        return false;
+    }
+
+    /**
+     * Sets up the UI.
+     */
+    private void prepareUi() {
+        if (mContactId != Contact.INVALID_ID) {
+            Contact contact = Contact.byId(mContactId);
+            ContactViewBinder.bind(contact, mTextContactName);
+        }
+        mTextMessageBody.setText(mBody);
+    }
+
+    /**
+     * Delegates selection of a {@Contact} to {@link SelectContactActivity}.
+     */
+    private void selectContact() {
+        Intent intent = new Intent(this, SelectContactActivity.class);
+        intent.setAction(SelectContactActivity.ACTION_SELECT_CONTACT);
+        startActivityForResult(intent, REQUEST_SELECT_CONTACT);
+    }
+
+    private View.OnClickListener mOnClickListener = new View.OnClickListener() {
+        @Override
+        public void onClick(View view) {
+            switch (view.getId()) {
+                case R.id.send:
+                    send();
+                    break;
+            }
+        }
+    };
+
+    /**
+     * Pretends to send the text to the contact. This only shows a dummy message.
+     */
+    private void send() {
+        Toast.makeText(this,
+                getString(R.string.message_sent, mBody, Contact.byId(mContactId).getName()),
+                Toast.LENGTH_SHORT).show();
+        finish();
+    }
+
+}
diff --git a/content/SharingShortcuts/Application/src/main/java/com/example/android/sharingshortcuts/SharingShortcutsManager.java b/content/SharingShortcuts/Application/src/main/java/com/example/android/sharingshortcuts/SharingShortcutsManager.java
new file mode 100644
index 0000000..14cc929
--- /dev/null
+++ b/content/SharingShortcuts/Application/src/main/java/com/example/android/sharingshortcuts/SharingShortcutsManager.java
@@ -0,0 +1,110 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR 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.sharingshortcuts;
+
+import android.content.Context;
+import android.content.Intent;
+
+import androidx.annotation.NonNull;
+import androidx.core.app.Person;
+import androidx.core.content.pm.ShortcutInfoCompat;
+import androidx.core.content.pm.ShortcutManagerCompat;
+import androidx.core.graphics.drawable.IconCompat;
+
+import java.util.ArrayList;
+import java.util.HashSet;
+import java.util.Set;
+
+/**
+ * Provides the Sharing Shortcuts items to the system.
+ * <p>
+ * Use the ShortcutManagerCompat to make it work on older Android versions
+ * without any extra work needed.
+ * <p>
+ * Interactions with the ShortcutManager API can happen on any thread.
+ */
+public class SharingShortcutsManager {
+
+    /**
+     * Define maximum number of shortcuts.
+     * Don't add more than {@link ShortcutManagerCompat#getMaxShortcutCountPerActivity(Context)}.
+     */
+    private static final int MAX_SHORTCUTS = 4;
+
+    /**
+     * Category name defined in res/xml/shortcuts.xml that accepts data of type text/plain
+     * and will trigger {@link SendMessageActivity}
+     */
+    private static final String CATEGORY_TEXT_SHARE_TARGET =
+            "com.example.android.sharingshortcuts.category.TEXT_SHARE_TARGET";
+
+    /**
+     * Publish the list of dynamics shortcuts that will be used in Direct Share.
+     * <p>
+     * For each shortcut, we specify the categories that it will be associated to,
+     * the intent that will trigger when opened as a static launcher shortcut,
+     * and the Shortcut ID between other things.
+     * <p>
+     * The Shortcut ID that we specify in the {@link ShortcutInfoCompat.Builder} constructor will
+     * be received in the intent as {@link Intent#EXTRA_SHORTCUT_ID}.
+     * <p>
+     * In this code sample, this method is completely static. We are always setting the same sharing
+     * shortcuts. In a real-world example, we would replace existing shortcuts depending on
+     * how the user interacts with the app as often as we want to.
+     */
+    public void pushDirectShareTargets(@NonNull Context context) {
+        ArrayList<ShortcutInfoCompat> shortcuts = new ArrayList<>();
+
+        // Category that our sharing shortcuts will be assigned to
+        Set<String> contactCategories = new HashSet<>();
+        contactCategories.add(CATEGORY_TEXT_SHARE_TARGET);
+
+        // Adding maximum number of shortcuts to the list
+        for (int id = 0; id < MAX_SHORTCUTS; ++id) {
+            Contact contact = Contact.byId(id);
+
+            // Item that will be sent if the shortcut is opened as a static launcher shortcut
+            Intent staticLauncherShortcutIntent = new Intent(Intent.ACTION_DEFAULT);
+
+            // Creates a new Sharing Shortcut and adds it to the list
+            // The id passed in the constructor will become EXTRA_SHORTCUT_ID in the received Intent
+            shortcuts.add(new ShortcutInfoCompat.Builder(context, Integer.toString(id))
+                    .setShortLabel(contact.getName())
+                    // Icon that will be displayed in the share target
+                    .setIcon(IconCompat.createWithResource(context, contact.getIcon()))
+                    .setIntent(staticLauncherShortcutIntent)
+                    // Make this sharing shortcut cached by the system
+                    // Even if it is unpublished, it can still appear on the sharesheet
+                    .setLongLived()
+                    .setCategories(contactCategories)
+                    // Person objects are used to give better suggestions
+                    .setPerson(new Person.Builder()
+                            .setName(contact.getName())
+                            .build())
+                    .build());
+        }
+
+        ShortcutManagerCompat.addDynamicShortcuts(context, shortcuts);
+    }
+
+    /**
+     * Remove all dynamic shortcuts
+     */
+    public void removeAllDirectShareTargets(@NonNull Context context) {
+        ShortcutManagerCompat.removeAllDynamicShortcuts(context);
+    }
+}
diff --git a/content/SharingShortcuts/Application/src/main/res/layout/activity_main.xml b/content/SharingShortcuts/Application/src/main/res/layout/activity_main.xml
new file mode 100644
index 0000000..524303d
--- /dev/null
+++ b/content/SharingShortcuts/Application/src/main/res/layout/activity_main.xml
@@ -0,0 +1,51 @@
+<?xml version="1.0" encoding="utf-8"?><!--
+  Copyright (C) 2019 The Android Open Source Project
+
+  Licensed under the Apache License, Version 2.0 (the "License");
+  you may not use this file except in compliance with the License.
+  You may obtain a copy of the License at
+
+       http://www.apache.org/licenses/LICENSE-2.0
+
+  Unless required by applicable law or agreed to in writing, software
+  distributed under the License is distributed on an "AS IS" BASIS,
+  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  See the License for the specific language governing permissions and
+  limitations under the License.
+  -->
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+    android:layout_width="match_parent"
+    android:layout_height="match_parent"
+    android:orientation="vertical">
+
+    <TextView
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        android:layout_margin="@dimen/margin_medium"
+        android:text="@string/explanation" />
+
+    <View
+        android:layout_width="match_parent"
+        android:layout_height="1dp"
+        android:background="@android:color/darker_gray" />
+
+    <EditText
+        android:id="@+id/body"
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        android:layout_marginStart="@dimen/margin_medium"
+        android:layout_marginTop="@dimen/margin_medium"
+        android:layout_marginEnd="@dimen/margin_medium"
+        android:hint="@string/text_to_share"
+        android:text="@string/hello" />
+
+    <Button
+        android:id="@+id/share"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:layout_gravity="end"
+        android:layout_marginStart="@dimen/margin_medium"
+        android:layout_marginEnd="@dimen/margin_medium"
+        android:text="@string/share" />
+
+</LinearLayout>
\ No newline at end of file
diff --git a/content/SharingShortcuts/Application/src/main/res/layout/activity_select_contact.xml b/content/SharingShortcuts/Application/src/main/res/layout/activity_select_contact.xml
new file mode 100644
index 0000000..b9cd803
--- /dev/null
+++ b/content/SharingShortcuts/Application/src/main/res/layout/activity_select_contact.xml
@@ -0,0 +1,24 @@
+<?xml version="1.0" encoding="utf-8"?><!--
+  Copyright (C) 2019 The Android Open Source Project
+
+  Licensed under the Apache License, Version 2.0 (the "License");
+  you may not use this file except in compliance with the License.
+  You may obtain a copy of the License at
+
+       http://www.apache.org/licenses/LICENSE-2.0
+
+  Unless required by applicable law or agreed to in writing, software
+  distributed under the License is distributed on an "AS IS" BASIS,
+  WITHOUT 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.recyclerview.widget.RecyclerView xmlns:android="http://schemas.android.com/apk/res/android"
+    android:id="@+id/recycler_view"
+    android:layout_width="match_parent"
+    android:layout_height="match_parent"
+    android:layout_marginTop="@dimen/margin_medium"
+    android:clipToPadding="false"
+    android:divider="@null"
+    android:paddingTop="@dimen/margin_tiny"
+    android:paddingBottom="@dimen/margin_tiny" />
diff --git a/content/SharingShortcuts/Application/src/main/res/layout/activity_send_message.xml b/content/SharingShortcuts/Application/src/main/res/layout/activity_send_message.xml
new file mode 100644
index 0000000..b8ad595
--- /dev/null
+++ b/content/SharingShortcuts/Application/src/main/res/layout/activity_send_message.xml
@@ -0,0 +1,84 @@
+<?xml version="1.0" encoding="utf-8"?><!--
+  Copyright (C) 2019 The Android Open Source Project
+
+  Licensed under the Apache License, Version 2.0 (the "License");
+  you may not use this file except in compliance with the License.
+  You may obtain a copy of the License at
+
+       http://www.apache.org/licenses/LICENSE-2.0
+
+  Unless required by applicable law or agreed to in writing, software
+  distributed under the License is distributed on an "AS IS" BASIS,
+  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  See the License for the specific language governing permissions and
+  limitations under the License.
+  -->
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:tools="http://schemas.android.com/tools"
+    android:layout_width="match_parent"
+    android:layout_height="match_parent"
+    android:orientation="vertical">
+
+    <LinearLayout
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        android:layout_marginStart="@dimen/dialog_padding"
+        android:layout_marginTop="@dimen/dialog_padding"
+        android:layout_marginEnd="@dimen/dialog_padding"
+        android:orientation="horizontal">
+
+        <TextView
+            android:layout_width="wrap_content"
+            android:layout_height="match_parent"
+            android:gravity="center_vertical"
+            android:text="@string/to"
+            android:textAppearance="@android:style/TextAppearance.Material.Caption" />
+
+        <TextView
+            android:id="@+id/contact_name"
+            android:layout_width="0dp"
+            android:layout_height="wrap_content"
+            android:layout_marginStart="@dimen/margin_medium"
+            android:layout_weight="1"
+            android:drawablePadding="@dimen/margin_medium"
+            android:gravity="center_vertical"
+            android:textAppearance="@android:style/TextAppearance.Material.Body1"
+            tools:drawableStart="@mipmap/logo_avatar"
+            tools:text="Taro" />
+
+    </LinearLayout>
+
+    <TextView
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:layout_marginStart="@dimen/dialog_padding"
+        android:layout_marginTop="@dimen/margin_medium"
+        android:layout_marginEnd="@dimen/dialog_padding"
+        android:gravity="center_vertical"
+        android:text="@string/body"
+        android:textAppearance="@android:style/TextAppearance.Material.Caption" />
+
+    <TextView
+        android:id="@+id/message_body"
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        android:layout_marginStart="@dimen/dialog_padding"
+        android:layout_marginEnd="@dimen/dialog_padding"
+        android:gravity="top"
+        android:hint="@string/hint_body"
+        android:paddingTop="@dimen/margin_small"
+        android:paddingBottom="@dimen/margin_small"
+        android:textAppearance="@android:style/TextAppearance.Material.Body1"
+        tools:text="Hello, world!" />
+
+    <Button
+        android:id="@+id/send"
+        style="@android:style/Widget.Material.Button.Borderless.Colored"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:layout_gravity="end"
+        android:layout_marginEnd="@dimen/dialog_button_padding"
+        android:layout_marginBottom="@dimen/dialog_button_padding"
+        android:text="@string/send" />
+
+</LinearLayout>
diff --git a/content/SharingShortcuts/Application/src/main/res/layout/item_contact.xml b/content/SharingShortcuts/Application/src/main/res/layout/item_contact.xml
new file mode 100644
index 0000000..a7c1735
--- /dev/null
+++ b/content/SharingShortcuts/Application/src/main/res/layout/item_contact.xml
@@ -0,0 +1,28 @@
+<?xml version="1.0" encoding="utf-8"?><!--
+  Copyright (C) 2019 The Android Open Source Project
+
+  Licensed under the Apache License, Version 2.0 (the "License");
+  you may not use this file except in compliance with the License.
+  You may obtain a copy of the License at
+
+       http://www.apache.org/licenses/LICENSE-2.0
+
+  Unless required by applicable law or agreed to in writing, software
+  distributed under the License is distributed on an "AS IS" BASIS,
+  WITHOUT 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"
+    xmlns:tools="http://schemas.android.com/tools"
+    android:id="@+id/contact_name"
+    android:layout_width="match_parent"
+    android:layout_height="wrap_content"
+    android:drawablePadding="@dimen/margin_medium"
+    android:gravity="center_vertical"
+    android:paddingStart="?android:attr/listPreferredItemPaddingStart"
+    android:paddingTop="@dimen/margin_tiny"
+    android:paddingEnd="?android:attr/listPreferredItemPaddingEnd"
+    android:paddingBottom="@dimen/margin_tiny"
+    tools:drawableStart="@mipmap/logo_avatar"
+    tools:text="Taro" />
diff --git a/content/SharingShortcuts/Application/src/main/res/mipmap-hdpi/ic_launcher.png b/content/SharingShortcuts/Application/src/main/res/mipmap-hdpi/ic_launcher.png
new file mode 100644
index 0000000..988f2ec
--- /dev/null
+++ b/content/SharingShortcuts/Application/src/main/res/mipmap-hdpi/ic_launcher.png
Binary files differ
diff --git a/content/SharingShortcuts/Application/src/main/res/mipmap-hdpi/logo_avatar.png b/content/SharingShortcuts/Application/src/main/res/mipmap-hdpi/logo_avatar.png
new file mode 100644
index 0000000..8892c08
--- /dev/null
+++ b/content/SharingShortcuts/Application/src/main/res/mipmap-hdpi/logo_avatar.png
Binary files differ
diff --git a/content/SharingShortcuts/Application/src/main/res/mipmap-mdpi/ic_launcher.png b/content/SharingShortcuts/Application/src/main/res/mipmap-mdpi/ic_launcher.png
new file mode 100644
index 0000000..0baa1cc
--- /dev/null
+++ b/content/SharingShortcuts/Application/src/main/res/mipmap-mdpi/ic_launcher.png
Binary files differ
diff --git a/content/SharingShortcuts/Application/src/main/res/mipmap-mdpi/logo_avatar.png b/content/SharingShortcuts/Application/src/main/res/mipmap-mdpi/logo_avatar.png
new file mode 100644
index 0000000..c2de774
--- /dev/null
+++ b/content/SharingShortcuts/Application/src/main/res/mipmap-mdpi/logo_avatar.png
Binary files differ
diff --git a/content/SharingShortcuts/Application/src/main/res/mipmap-xhdpi/ic_launcher.png b/content/SharingShortcuts/Application/src/main/res/mipmap-xhdpi/ic_launcher.png
new file mode 100644
index 0000000..da0aa2f
--- /dev/null
+++ b/content/SharingShortcuts/Application/src/main/res/mipmap-xhdpi/ic_launcher.png
Binary files differ
diff --git a/content/SharingShortcuts/Application/src/main/res/mipmap-xhdpi/logo_avatar.png b/content/SharingShortcuts/Application/src/main/res/mipmap-xhdpi/logo_avatar.png
new file mode 100644
index 0000000..10c2dc9
--- /dev/null
+++ b/content/SharingShortcuts/Application/src/main/res/mipmap-xhdpi/logo_avatar.png
Binary files differ
diff --git a/content/SharingShortcuts/Application/src/main/res/mipmap-xxhdpi/ic_launcher.png b/content/SharingShortcuts/Application/src/main/res/mipmap-xxhdpi/ic_launcher.png
new file mode 100644
index 0000000..e1cc1ff
--- /dev/null
+++ b/content/SharingShortcuts/Application/src/main/res/mipmap-xxhdpi/ic_launcher.png
Binary files differ
diff --git a/content/SharingShortcuts/Application/src/main/res/mipmap-xxhdpi/logo_avatar.png b/content/SharingShortcuts/Application/src/main/res/mipmap-xxhdpi/logo_avatar.png
new file mode 100644
index 0000000..df02f04
--- /dev/null
+++ b/content/SharingShortcuts/Application/src/main/res/mipmap-xxhdpi/logo_avatar.png
Binary files differ
diff --git a/content/SharingShortcuts/Application/src/main/res/mipmap-xxxhdpi/ic_launcher.png b/content/SharingShortcuts/Application/src/main/res/mipmap-xxxhdpi/ic_launcher.png
new file mode 100644
index 0000000..8b0f60c
--- /dev/null
+++ b/content/SharingShortcuts/Application/src/main/res/mipmap-xxxhdpi/ic_launcher.png
Binary files differ
diff --git a/content/SharingShortcuts/Application/src/main/res/mipmap-xxxhdpi/logo_avatar.png b/content/SharingShortcuts/Application/src/main/res/mipmap-xxxhdpi/logo_avatar.png
new file mode 100644
index 0000000..dc8d376
--- /dev/null
+++ b/content/SharingShortcuts/Application/src/main/res/mipmap-xxxhdpi/logo_avatar.png
Binary files differ
diff --git a/content/SharingShortcuts/Application/src/main/res/values/colors.xml b/content/SharingShortcuts/Application/src/main/res/values/colors.xml
new file mode 100644
index 0000000..4ffc20b
--- /dev/null
+++ b/content/SharingShortcuts/Application/src/main/res/values/colors.xml
@@ -0,0 +1,25 @@
+<?xml version="1.0" encoding="utf-8"?><!--
+  Copyright (C) 2019 The Android Open Source Project
+
+  Licensed under the Apache License, Version 2.0 (the "License");
+  you may not use this file except in compliance with the License.
+  You may obtain a copy of the License at
+
+       http://www.apache.org/licenses/LICENSE-2.0
+
+  Unless required by applicable law or agreed to in writing, software
+  distributed under the License is distributed on an "AS IS" BASIS,
+  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  See the License for the specific language governing permissions and
+  limitations under the License.
+  -->
+<resources>
+    <color name="primary">#3F51B5</color>
+    <color name="primary_dark">#303F9F</color>
+    <color name="primary_light">#C5CAE9</color>
+    <color name="accent">#00BCD4</color>
+    <color name="primary_text">#212121</color>
+    <color name="secondary_text">#727272</color>
+    <color name="icons">#FFFFFF</color>
+    <color name="divider">#B6B6B6</color>
+</resources>
diff --git a/content/SharingShortcuts/Application/src/main/res/values/dimens.xml b/content/SharingShortcuts/Application/src/main/res/values/dimens.xml
new file mode 100644
index 0000000..dce8253
--- /dev/null
+++ b/content/SharingShortcuts/Application/src/main/res/values/dimens.xml
@@ -0,0 +1,19 @@
+<?xml version="1.0" encoding="utf-8"?><!--
+  Copyright (C) 2019 The Android Open Source Project
+
+  Licensed under the Apache License, Version 2.0 (the "License");
+  you may not use this file except in compliance with the License.
+  You may obtain a copy of the License at
+
+       http://www.apache.org/licenses/LICENSE-2.0
+
+  Unless required by applicable law or agreed to in writing, software
+  distributed under the License is distributed on an "AS IS" BASIS,
+  WITHOUT 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="dialog_padding">24dp</dimen>
+    <dimen name="dialog_button_padding">8dp</dimen>
+</resources>
\ No newline at end of file
diff --git a/content/SharingShortcuts/Application/src/main/res/values/strings.xml b/content/SharingShortcuts/Application/src/main/res/values/strings.xml
new file mode 100644
index 0000000..2fd9ffa
--- /dev/null
+++ b/content/SharingShortcuts/Application/src/main/res/values/strings.xml
@@ -0,0 +1,38 @@
+<?xml version="1.0" encoding="utf-8"?><!--
+  Copyright (C) 2019 The Android Open Source Project
+
+  Licensed under the Apache License, Version 2.0 (the "License");
+  you may not use this file except in compliance with the License.
+  You may obtain a copy of the License at
+
+       http://www.apache.org/licenses/LICENSE-2.0
+
+  Unless required by applicable law or agreed to in writing, software
+  distributed under the License is distributed on an "AS IS" BASIS,
+  WITHOUT 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>
+
+    <!-- MainActivity -->
+
+    <string name="send_intent_title">Send message</string>
+    <string name="explanation">
+        This app demonstrates how to implement Direct Share. Use some other app and share a text.
+        For your convenience, you can also use the input below to share the text.
+    </string>
+    <string name="text_to_share">Text to share</string>
+    <string name="hello">Hello!</string>
+    <string name="share">Share</string>
+
+    <!-- SendMessageActivity -->
+
+    <string name="sending_message">Sending a message</string>
+    <string name="to">To:</string>
+    <string name="send">Send</string>
+    <string name="body">Body:</string>
+    <string name="hint_body">Edit your message</string>
+    <string name="message_sent">Sent a message \"%1$s\" to %2$s</string>
+
+</resources>
\ No newline at end of file
diff --git a/content/SharingShortcuts/Application/src/main/res/values/styles.xml b/content/SharingShortcuts/Application/src/main/res/values/styles.xml
new file mode 100644
index 0000000..ca1a0e7
--- /dev/null
+++ b/content/SharingShortcuts/Application/src/main/res/values/styles.xml
@@ -0,0 +1,31 @@
+<?xml version="1.0" encoding="utf-8"?><!--
+  Copyright (C) 2019 The Android Open Source Project
+
+  Licensed under the Apache License, Version 2.0 (the "License");
+  you may not use this file except in compliance with the License.
+  You may obtain a copy of the License at
+
+       http://www.apache.org/licenses/LICENSE-2.0
+
+  Unless required by applicable law or agreed to in writing, software
+  distributed under the License is distributed on an "AS IS" BASIS,
+  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  See the License for the specific language governing permissions and
+  limitations under the License.
+  -->
+<resources>
+
+    <style name="SharingShortcutsTheme" parent="android:Theme.Material.Light.DarkActionBar">
+        <item name="android:colorPrimary">@color/primary</item>
+        <item name="android:colorPrimaryDark">@color/primary_dark</item>
+        <item name="android:colorAccent">@color/accent</item>
+    </style>
+
+    <style name="SharingShortcutsDialogTheme" parent="android:Theme.Material.Light.Dialog">
+        <item name="android:colorPrimary">@color/primary</item>
+        <item name="android:colorPrimaryDark">@color/primary_dark</item>
+        <item name="android:colorAccent">@color/accent</item>
+    </style>
+
+
+</resources>
\ No newline at end of file
diff --git a/content/SharingShortcuts/Application/src/main/res/xml/file_paths.xml b/content/SharingShortcuts/Application/src/main/res/xml/file_paths.xml
new file mode 100644
index 0000000..5d344b3
--- /dev/null
+++ b/content/SharingShortcuts/Application/src/main/res/xml/file_paths.xml
@@ -0,0 +1,20 @@
+<?xml version="1.0" encoding="utf-8"?><!--
+  Copyright (C) 2019 The Android Open Source Project
+
+  Licensed under the Apache License, Version 2.0 (the "License");
+  you may not use this file except in compliance with the License.
+  You may obtain a copy of the License at
+
+       http://www.apache.org/licenses/LICENSE-2.0
+
+  Unless required by applicable law or agreed to in writing, software
+  distributed under the License is distributed on an "AS IS" BASIS,
+  WITHOUT 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>
+    <cache-path
+        name="shared_images"
+        path="images/" />
+</paths>
diff --git a/content/SharingShortcuts/Application/src/main/res/xml/shortcuts.xml b/content/SharingShortcuts/Application/src/main/res/xml/shortcuts.xml
new file mode 100644
index 0000000..a715cd3
--- /dev/null
+++ b/content/SharingShortcuts/Application/src/main/res/xml/shortcuts.xml
@@ -0,0 +1,21 @@
+<?xml version="1.0" encoding="utf-8"?><!--
+  Copyright (C) 2019 The Android Open Source Project
+
+  Licensed under the Apache License, Version 2.0 (the "License");
+  you may not use this file except in compliance with the License.
+  You may obtain a copy of the License at
+
+       http://www.apache.org/licenses/LICENSE-2.0
+
+  Unless required by applicable law or agreed to in writing, software
+  distributed under the License is distributed on an "AS IS" BASIS,
+  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  See the License for the specific language governing permissions and
+  limitations under the License.
+  -->
+<shortcuts xmlns:android="http://schemas.android.com/apk/res/android">
+    <share-target android:targetClass="com.example.android.sharingshortcuts.SendMessageActivity">
+        <data android:mimeType="text/plain" />
+        <category android:name="com.example.android.sharingshortcuts.category.TEXT_SHARE_TARGET" />
+    </share-target>
+</shortcuts>
\ No newline at end of file
diff --git a/content/SharingShortcuts/build.gradle b/content/SharingShortcuts/build.gradle
new file mode 100644
index 0000000..9fa5685
--- /dev/null
+++ b/content/SharingShortcuts/build.gradle
@@ -0,0 +1,21 @@
+
+
+// BEGIN_EXCLUDE
+import com.example.android.samples.build.SampleGenPlugin
+apply plugin: SampleGenPlugin
+
+samplegen {
+  pathToBuild "../../../../build"
+  pathToSamplesCommon "../../common"
+}
+apply from: "../../../../build/build.gradle"
+buildscript {
+  ext.kotlin_version = '1.3.21'
+  repositories {
+    mavenCentral()
+  }
+  dependencies {
+    classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
+  }
+}
+// END_EXCLUDE
diff --git a/content/SharingShortcuts/buildSrc/build.gradle b/content/SharingShortcuts/buildSrc/build.gradle
new file mode 100644
index 0000000..75b00ee
--- /dev/null
+++ b/content/SharingShortcuts/buildSrc/build.gradle
@@ -0,0 +1,17 @@
+
+repositories {
+    google()
+    jcenter()
+}
+dependencies {
+    implementation 'org.freemarker:freemarker:2.3.20'
+}
+
+sourceSets {
+    main {
+        groovy {
+            srcDir new File(rootDir, "../../../../../build/buildSrc/src/main/groovy")
+        }
+    }
+}
+
diff --git a/content/SharingShortcuts/gradle.properties b/content/SharingShortcuts/gradle.properties
new file mode 100644
index 0000000..94f8472
--- /dev/null
+++ b/content/SharingShortcuts/gradle.properties
@@ -0,0 +1,22 @@
+
+# Project-wide Gradle settings.
+
+# IDE (e.g. Android Studio) users:
+# Settings specified in this file will override any Gradle settings
+# configured through the IDE.
+
+# 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.
+# Default value: -Xmx10248m -XX:MaxPermSize=256m
+# org.gradle.jvmargs=-Xmx2048m -XX:MaxPermSize=512m -XX:+HeapDumpOnOutOfMemoryError -Dfile.encoding=UTF-8
+
+# 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
+
+android.useAndroidX=true
+android.enableJetifier=true
diff --git a/content/SharingShortcuts/gradle/wrapper/gradle-wrapper.jar b/content/SharingShortcuts/gradle/wrapper/gradle-wrapper.jar
new file mode 100644
index 0000000..8c0fb64
--- /dev/null
+++ b/content/SharingShortcuts/gradle/wrapper/gradle-wrapper.jar
Binary files differ
diff --git a/content/SharingShortcuts/gradle/wrapper/gradle-wrapper.properties b/content/SharingShortcuts/gradle/wrapper/gradle-wrapper.properties
new file mode 100644
index 0000000..77cff65
--- /dev/null
+++ b/content/SharingShortcuts/gradle/wrapper/gradle-wrapper.properties
@@ -0,0 +1,6 @@
+#Mon Feb 18 15:19:26 CET 2019
+distributionBase=GRADLE_USER_HOME
+distributionPath=wrapper/dists
+zipStoreBase=GRADLE_USER_HOME
+zipStorePath=wrapper/dists
+distributionUrl=https\://services.gradle.org/distributions/gradle-4.10.1-all.zip
diff --git a/content/SharingShortcuts/gradlew b/content/SharingShortcuts/gradlew
new file mode 100755
index 0000000..91a7e26
--- /dev/null
+++ b/content/SharingShortcuts/gradlew
@@ -0,0 +1,164 @@
+#!/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
+
+# For Cygwin, ensure paths are in UNIX format before anything is touched.
+if $cygwin ; then
+    [ -n "$JAVA_HOME" ] && JAVA_HOME=`cygpath --unix "$JAVA_HOME"`
+fi
+
+# 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\"`/" >&-
+APP_HOME="`pwd -P`"
+cd "$SAVED" >&-
+
+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"`
+
+    # 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/content/SharingShortcuts/gradlew.bat b/content/SharingShortcuts/gradlew.bat
new file mode 100644
index 0000000..aec9973
--- /dev/null
+++ b/content/SharingShortcuts/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/content/SharingShortcuts/screenshots/1-main.png b/content/SharingShortcuts/screenshots/1-main.png
new file mode 100644
index 0000000..693b2be
--- /dev/null
+++ b/content/SharingShortcuts/screenshots/1-main.png
Binary files differ
diff --git a/content/SharingShortcuts/screenshots/2-intent.png b/content/SharingShortcuts/screenshots/2-intent.png
new file mode 100644
index 0000000..f01e395
--- /dev/null
+++ b/content/SharingShortcuts/screenshots/2-intent.png
Binary files differ
diff --git a/content/SharingShortcuts/screenshots/3-message.png b/content/SharingShortcuts/screenshots/3-message.png
new file mode 100644
index 0000000..cde2496
--- /dev/null
+++ b/content/SharingShortcuts/screenshots/3-message.png
Binary files differ
diff --git a/content/SharingShortcuts/screenshots/4-static_shortcuts.png b/content/SharingShortcuts/screenshots/4-static_shortcuts.png
new file mode 100644
index 0000000..0404027
--- /dev/null
+++ b/content/SharingShortcuts/screenshots/4-static_shortcuts.png
Binary files differ
diff --git a/content/SharingShortcuts/screenshots/icon-web.png b/content/SharingShortcuts/screenshots/icon-web.png
new file mode 100644
index 0000000..ee7c557
--- /dev/null
+++ b/content/SharingShortcuts/screenshots/icon-web.png
Binary files differ
diff --git a/content/SharingShortcuts/settings.gradle b/content/SharingShortcuts/settings.gradle
new file mode 100644
index 0000000..0a5c310
--- /dev/null
+++ b/content/SharingShortcuts/settings.gradle
@@ -0,0 +1,2 @@
+
+include 'Application'
diff --git a/content/SharingShortcuts/template-params.xml b/content/SharingShortcuts/template-params.xml
new file mode 100644
index 0000000..5bf5626
--- /dev/null
+++ b/content/SharingShortcuts/template-params.xml
@@ -0,0 +1,114 @@
+<?xml version="1.0" encoding="UTF-8"?><!--
+ Copyright 2013 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+     http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+
+<sample>
+    <name>SharingShortcuts</name>
+    <group>Content</group>
+    <package>com.example.android.sharingshortcuts</package>
+
+    <minSdk>21</minSdk>
+
+    <!-- Include additional dependencies here.-->
+    <dependency>androidx.appcompat:appcompat:1.1.0-alpha03</dependency>
+    <dependency>androidx.sharetarget:sharetarget:1.0.0-alpha01</dependency>
+    <dependency>androidx.recyclerview:recyclerview:1.0.0</dependency>
+
+    <strings>
+        <intro>
+            <![CDATA[
+This sample demonstrates how to provide the Sharing Shortcuts - Direct Share feature. The app shows
+some options directly in the list of share intent candidates and launcher shortcuts.
+            ]]>
+        </intro>
+    </strings>
+
+    <template src="base" />
+    <androidX>true</androidX>
+
+    <metadata>
+        <!-- Values: {DRAFT | PUBLISHED | INTERNAL | DEPRECATED | SUPERCEDED} -->
+        <status>DRAFT</status>
+        <categories>Content</categories>
+        <technologies>Android</technologies>
+        <languages>Java</languages>
+        <solutions>Mobile</solutions>
+        <level>INTERMEDIATE</level>
+        <icon>screenshots/icon-web.png</icon>
+        <screenshots>
+            <img>screenshots/1-main.png</img>
+            <img>screenshots/2-intent.png</img>
+            <img>screenshots/3-message.png</img>
+            <img>screenshots/4-static_shortcuts.png</img>
+        </screenshots>
+
+        <api_refs>
+            <androidx>androidx.core.content.pm.ShortcutManagerCompat</androidx>
+            <androidx>androidx.core.content.pm.ShortcutInfoCompat</androidx>
+        </api_refs>
+        <description>
+            <![CDATA[
+Sample demonstrating how to show some options directly in the list of share intent candidates.
+            ]]>
+        </description>
+
+        <intro>
+            <![CDATA[
+Direct Share is a feature that allows apps to show their internal options directly in the
+system Intent chooser dialog. This sample is a dummy messaging app, and just like any other
+messaging apps, it receives intents for sharing a plain text. When a user shares some text from some
+other app, this sample app will be listed as an option. Using the Direct Share feature, this app
+also shows some of contacts directly in the chooser dialog.
+
+Direct Share was [first introduced][1] in Android M where you had to implement a service to provide
+direct share targets on demand. The way to do that changed in Android Q, you need to provide your
+direct share targets in advance. First, you have to declare share-target elements in the same
+application's resource file than [static shortcuts][2]. Then, you need to publish dynamic
+shortcuts with the same category you declared in the share-target with the [ShortcutManager API][3].
+You will need to manually update the list of shortcuts every time you consider it appropriate.
+The API offers methods to update, remove or add shortcuts. You can use the
+[ShortcutInfoCompat.Builder][4] to customize your shortcut. If you don't want to block the UI thread
+doing these operations, interactions with the ShortcutManager can also happen on a background thread.
+
+There are three ways the app is sending/receiving intents:
+- Dynamic Share: The user selected the app in the sharesheet to receive the text. After this,
+the user will have to select the contact to share the text with. In our case, the app receives an
+intent of type Intent.ACTION_SEND.
+- Direct Share: The user selected a person of your app in the sharesheet to share the text with.
+The received intent of type Intent.ACTION_SEND will contain a String EXTRA_SHORTCUT_ID that will
+have the id of the shortcut that was selected. After this, the app is ready to send the text.
+- Launcher shortcut: When the user taps on a launcher shortcut, the intent that was
+added to the shortcut will get fired. In our case, it triggers an intent of type Intent.ACTION_DEFAULT.
+
+To make Direct Share backwards compatible with older Android versions, you need to add the AndroidX
+sharetarget library and in your AndroidManifest.xml, add a meta-data tag in your Activity that
+receives the Intent. Specify android:name as android.service.chooser.chooser_target_service and
+android:value as androidx.sharetarget.ChooserTargetServiceCompat.
+
+The way to share text has also changed. Before, you could specify a title in the
+[Intent.createChooser()][5] method itself. That is deprecated and unused in Android Q. You can achieve
+the same behavior by adding an Intent.EXTRA_TITLE extra to the intent. Similarly, if you want a
+preview thumbnail to appear, you can create a content URI and set a ClipData object in the intent.
+You can see how to do that in our example, open the MainActivity.java file for more details.
+
+[1]: https://developer.android.com/about/versions/marshmallow/android-6.0#direct-share
+[2]: https://developer.android.com/guide/topics/ui/shortcuts/creating-shortcuts
+[3]: https://developer.android.com/reference/androidx/core/content/pm/ShortcutManagerCompat.html
+[4]: https://developer.android.com/reference/androidx/core/content/pm/ShortcutInfoCompat.Builder.html
+[5]: https://developer.android.com/reference/android/content/Intent.html#createChooser(android.content.Intent,%20java.lang.CharSequence)
+            ]]>
+        </intro>
+    </metadata>
+</sample>
diff --git a/media/MediaBrowserService/template-params.xml b/media/MediaBrowserService/template-params.xml
index 9abe212..b7f6f72 100644
--- a/media/MediaBrowserService/template-params.xml
+++ b/media/MediaBrowserService/template-params.xml
@@ -29,11 +29,7 @@
 
     <strings>
         <intro>
-            <![CDATA[
-This sample shows how to implement an audio media app that provides
-media library metadata and playback controls through a standard
-service. It exposes a simple music library through the new
-MediaBrowserService and provides MediaSession callbacks.]]>
+            <![CDATA[ This sample is DEPRECATED. ]]>
         </intro>
     </strings>
 
@@ -42,7 +38,7 @@
     <template src="base-application" />
 
     <metadata>
-    <status>PUBLISHED</status>
+    <status>DEPRECATED</status>
     <categories>Media</categories>
     <technologies>Android</technologies>
     <languages>Java</languages>
@@ -62,6 +58,10 @@
     </api_refs>
     <description>
 <![CDATA[
+This sample is deprecated. See the
+[Universal Android Music Player](https://github.com/googlesamples/android-UniversalMusicPlayer)
+sample for a more complete solution for using a MediaSession and MediaBrowserService.
+
 This sample shows how to implement a media app that allows
 background playback of audio, and provide a media library
 that is exposed to other apps.
diff --git a/notification/Bubbles/.gitignore b/notification/Bubbles/.gitignore
new file mode 100644
index 0000000..603b140
--- /dev/null
+++ b/notification/Bubbles/.gitignore
@@ -0,0 +1,14 @@
+*.iml
+.gradle
+/local.properties
+/.idea/caches
+/.idea/libraries
+/.idea/modules.xml
+/.idea/workspace.xml
+/.idea/navEditor.xml
+/.idea/assetWizardSettings.xml
+.DS_Store
+/build
+/captures
+.externalNativeBuild
+.cxx
diff --git a/notification/Bubbles/app/.gitignore b/notification/Bubbles/app/.gitignore
new file mode 100644
index 0000000..796b96d
--- /dev/null
+++ b/notification/Bubbles/app/.gitignore
@@ -0,0 +1 @@
+/build
diff --git a/notification/Bubbles/app/build.gradle b/notification/Bubbles/app/build.gradle
new file mode 100644
index 0000000..1600ee4
--- /dev/null
+++ b/notification/Bubbles/app/build.gradle
@@ -0,0 +1,50 @@
+apply plugin: 'com.android.application'
+apply plugin: 'kotlin-android'
+
+android {
+    compileSdkVersion 'android-Q'
+    defaultConfig {
+        applicationId "com.example.android.bubbles"
+        minSdkVersion 'Q'
+        targetSdkVersion 'Q'
+        versionCode 1
+        versionName '1.0'
+        testInstrumentationRunner 'androidx.test.runner.AndroidJUnitRunner'
+    }
+    buildTypes {
+        release {
+            minifyEnabled false
+            proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
+        }
+    }
+}
+
+dependencies {
+    implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version"
+
+    implementation 'androidx.appcompat:appcompat:1.0.2'
+    implementation 'androidx.fragment:fragment-ktx:1.0.0'
+    implementation 'androidx.core:core-ktx:1.0.1'
+    implementation 'androidx.constraintlayout:constraintlayout:1.1.3'
+    implementation 'androidx.recyclerview:recyclerview:1.0.0'
+
+    def lifecycle_version = '2.0.0'
+    implementation "androidx.lifecycle:lifecycle-extensions:$lifecycle_version"
+    implementation "androidx.lifecycle:lifecycle-viewmodel-ktx:$lifecycle_version"
+    testImplementation "androidx.arch.core:core-testing:$lifecycle_version"
+
+    implementation 'com.google.android.material:material:1.0.0'
+
+    implementation 'com.github.bumptech.glide:glide:4.9.0'
+
+    testImplementation 'junit:junit:4.12'
+    androidTestImplementation 'androidx.test.ext:junit:1.1.0'
+    androidTestImplementation 'androidx.test.espresso:espresso-core:3.1.1'
+
+    testImplementation 'org.robolectric:robolectric:4.2'
+    testImplementation "androidx.arch.core:core-testing:$lifecycle_version"
+    testImplementation 'androidx.test.ext:junit:1.1.0'
+    testImplementation 'androidx.test.espresso:espresso-core:3.1.1'
+    testImplementation 'androidx.test.ext:truth:1.1.0'
+    testImplementation 'com.google.truth:truth:0.42'
+}
diff --git a/notification/Bubbles/app/proguard-rules.pro b/notification/Bubbles/app/proguard-rules.pro
new file mode 100644
index 0000000..f1b4245
--- /dev/null
+++ b/notification/Bubbles/app/proguard-rules.pro
@@ -0,0 +1,21 @@
+# Add project specific ProGuard rules here.
+# You can control the set of applied configuration files using the
+# proguardFiles setting in build.gradle.
+#
+# For more details, see
+#   http://developer.android.com/guide/developing/tools/proguard.html
+
+# If your project uses WebView with JS, uncomment the following
+# and specify the fully qualified class name to the JavaScript interface
+# class:
+#-keepclassmembers class fqcn.of.javascript.interface.for.webview {
+#   public *;
+#}
+
+# Uncomment this to preserve the line number information for
+# debugging stack traces.
+#-keepattributes SourceFile,LineNumberTable
+
+# If you keep the line number information, uncomment this to
+# hide the original source file name.
+#-renamesourcefileattribute SourceFile
diff --git a/notification/Bubbles/app/src/androidTest/java/com/example/android/bubbles/BubbleActivityTest.kt b/notification/Bubbles/app/src/androidTest/java/com/example/android/bubbles/BubbleActivityTest.kt
new file mode 100644
index 0000000..81a8fde
--- /dev/null
+++ b/notification/Bubbles/app/src/androidTest/java/com/example/android/bubbles/BubbleActivityTest.kt
@@ -0,0 +1,44 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *       http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR 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.bubbles
+
+import android.app.Application
+import android.content.Intent
+import android.net.Uri
+import androidx.test.core.app.ActivityScenario
+import androidx.test.core.app.ApplicationProvider
+import androidx.test.espresso.Espresso.onView
+import androidx.test.espresso.assertion.ViewAssertions.matches
+import androidx.test.espresso.matcher.ViewMatchers.isDisplayed
+import androidx.test.espresso.matcher.ViewMatchers.withHint
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@RunWith(AndroidJUnit4::class)
+class BubbleActivityTest {
+
+    @Test
+    fun showsChatFragment() {
+        ActivityScenario.launch<BubbleActivity>(
+            Intent(ApplicationProvider.getApplicationContext<Application>(), BubbleActivity::class.java)
+                .setAction(Intent.ACTION_VIEW)
+                .setData(Uri.parse("https://android.example.com/chat/1"))
+        ).use {
+            onView(withHint("Type a message…")).check(matches(isDisplayed()))
+        }
+    }
+}
diff --git a/notification/Bubbles/app/src/androidTest/java/com/example/android/bubbles/MainActivityTest.kt b/notification/Bubbles/app/src/androidTest/java/com/example/android/bubbles/MainActivityTest.kt
new file mode 100644
index 0000000..c817477
--- /dev/null
+++ b/notification/Bubbles/app/src/androidTest/java/com/example/android/bubbles/MainActivityTest.kt
@@ -0,0 +1,41 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *       http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR 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.bubbles
+
+import androidx.test.core.app.ActivityScenario
+import androidx.test.espresso.Espresso.onView
+import androidx.test.espresso.action.ViewActions.click
+import androidx.test.espresso.assertion.ViewAssertions.matches
+import androidx.test.espresso.matcher.ViewMatchers.isDisplayed
+import androidx.test.espresso.matcher.ViewMatchers.withHint
+import androidx.test.espresso.matcher.ViewMatchers.withText
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@RunWith(AndroidJUnit4::class)
+class MainActivityTest {
+
+    @Test
+    fun navigateToChatFragment() {
+        ActivityScenario.launch(MainActivity::class.java).use {
+            onView(withText("Cat"))
+                .check(matches(isDisplayed()))
+                .perform(click())
+            onView(withHint("Type a message…")).check(matches(isDisplayed()))
+        }
+    }
+}
diff --git a/notification/Bubbles/app/src/main/AndroidManifest.xml b/notification/Bubbles/app/src/main/AndroidManifest.xml
new file mode 100644
index 0000000..45fac5e
--- /dev/null
+++ b/notification/Bubbles/app/src/main/AndroidManifest.xml
@@ -0,0 +1,91 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  Copyright (C) 2019 The Android Open Source Project
+
+  Licensed under the Apache License, Version 2.0 (the "License");
+  you may not use this file except in compliance with the License.
+  You may obtain a copy of the License at
+
+       http://www.apache.org/licenses/LICENSE-2.0
+
+  Unless required by applicable law or agreed to in writing, software
+  distributed under the License is distributed on an "AS IS" BASIS,
+  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  See the License for the specific language governing permissions and
+  limitations under the License.
+-->
+<manifest
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:tools="http://schemas.android.com/tools"
+    package="com.example.android.bubbles">
+
+    <application
+        android:allowBackup="false"
+        android:icon="@mipmap/ic_launcher"
+        android:label="@string/app_name"
+        android:roundIcon="@mipmap/ic_launcher_round"
+        android:supportsRtl="true"
+        android:theme="@style/Theme.Bubbles"
+        tools:ignore="GoogleAppIndexingWarning">
+
+        <!--
+            Our main entry point.
+        -->
+        <activity
+            android:name=".MainActivity"
+            android:launchMode="singleTop">
+            <!--
+                This activity is the one that's shown on the launcher.
+            -->
+            <intent-filter>
+                <action android:name="android.intent.action.MAIN" />
+                <category android:name="android.intent.category.LAUNCHER" />
+            </intent-filter>
+            <!--
+                This is used as the content URI of notifications. It navigates directly to the specified chat screen.
+            -->
+            <intent-filter>
+                <action android:name="android.intent.action.VIEW" />
+                <category android:name="android.intent.category.BROWSABLE" />
+                <data
+                    android:host="android.example.com"
+                    android:pathPattern="/chat/*"
+                    android:scheme="https" />
+            </intent-filter>
+        </activity>
+
+        <!--
+            The dummy voice-call screen.
+            This Activity can be launched from inside an expanded Bubble. Since this Activity is launched as a new task,
+            it is opened as a full Activity, rather than stacked inside the expanded Bubble.
+        -->
+        <activity
+            android:name=".VoiceCallActivity"
+            android:launchMode="singleInstance"
+            android:theme="@style/Theme.Bubbles.Voice" />
+
+        <!--
+            This Activity is the expanded Bubble. For that, this Activity has to have several attributes.
+            - allowEmbedded="true": The expanded Bubble is embedded in the System UI.
+            - resizeableActivity="true": The expanded Bubble is resized by the System UI.
+            - documentLaunchMode="always": We show multiple bubbles in this sample. There will be multiple instances of
+                                           this Activity.
+        -->
+        <activity
+            android:name=".BubbleActivity"
+            android:allowEmbedded="true"
+            android:documentLaunchMode="always"
+            android:resizeableActivity="true">
+            <intent-filter>
+                <action android:name="android.intent.action.VIEW" />
+                <category android:name="android.intent.category.BROWSABLE" />
+                <data
+                    android:host="android.example.com"
+                    android:pathPattern="/chat/*"
+                    android:scheme="https" />
+            </intent-filter>
+        </activity>
+
+    </application>
+
+</manifest>
diff --git a/notification/Bubbles/app/src/main/java/com/example/android/bubbles/BubbleActivity.kt b/notification/Bubbles/app/src/main/java/com/example/android/bubbles/BubbleActivity.kt
new file mode 100644
index 0000000..bae103a
--- /dev/null
+++ b/notification/Bubbles/app/src/main/java/com/example/android/bubbles/BubbleActivity.kt
@@ -0,0 +1,58 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *       http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR 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.bubbles
+
+import android.os.Bundle
+import android.widget.ImageView
+import android.widget.TextView
+import androidx.appcompat.app.AppCompatActivity
+import androidx.fragment.app.transaction
+import com.example.android.bubbles.ui.chat.ChatFragment
+import com.example.android.bubbles.ui.photo.PhotoFragment
+
+/**
+ * Entry point of the app when it is launched as an expanded Bubble.
+ */
+class BubbleActivity : AppCompatActivity(), NavigationController {
+
+    override fun onCreate(savedInstanceState: Bundle?) {
+        super.onCreate(savedInstanceState)
+        setContentView(R.layout.bubble_activity)
+        val id = intent.data.lastPathSegment.toLongOrNull() ?: return
+        if (savedInstanceState == null) {
+            supportFragmentManager.transaction(now = true) {
+                replace(R.id.container, ChatFragment.newInstance(id, false))
+            }
+        }
+    }
+
+    override fun openChat(id: Long) {
+        throw UnsupportedOperationException("BubbleActivity always shows a single chat thread.")
+    }
+
+    override fun openPhoto(photo: Int) {
+        // In an expanded Bubble, you can navigate between Fragments just like you would normally do in a normal
+        // Activity. Just make sure you don't block onBackPressed().
+        supportFragmentManager.transaction {
+            addToBackStack(null)
+            replace(R.id.container, PhotoFragment.newInstance(photo))
+        }
+    }
+
+    override fun updateAppBar(showContact: Boolean, hidden: Boolean, body: (name: TextView, icon: ImageView) -> Unit) {
+        // The expanded bubble does not have an app bar. Ignore.
+    }
+}
diff --git a/notification/Bubbles/app/src/main/java/com/example/android/bubbles/MainActivity.kt b/notification/Bubbles/app/src/main/java/com/example/android/bubbles/MainActivity.kt
new file mode 100644
index 0000000..c01f4f7
--- /dev/null
+++ b/notification/Bubbles/app/src/main/java/com/example/android/bubbles/MainActivity.kt
@@ -0,0 +1,115 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *       http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR 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.bubbles
+
+import android.content.Intent
+import android.os.Bundle
+import android.transition.Transition
+import android.transition.TransitionInflater
+import android.transition.TransitionManager
+import android.view.View
+import android.view.ViewGroup
+import android.widget.ImageView
+import android.widget.TextView
+import androidx.appcompat.app.AppCompatActivity
+import androidx.fragment.app.FragmentManager
+import androidx.fragment.app.transaction
+import com.example.android.bubbles.ui.chat.ChatFragment
+import com.example.android.bubbles.ui.main.MainFragment
+import com.example.android.bubbles.ui.photo.PhotoFragment
+
+/**
+ * Entry point of the app when it is launched as a full app.
+ */
+class MainActivity : AppCompatActivity(), NavigationController {
+
+    companion object {
+        private const val FRAGMENT_CHAT = "chat"
+    }
+
+    private lateinit var appBar: ViewGroup
+    private lateinit var name: TextView
+    private lateinit var icon: ImageView
+
+    private lateinit var transition: Transition
+
+    override fun onCreate(savedInstanceState: Bundle?) {
+        super.onCreate(savedInstanceState)
+        setContentView(R.layout.main_activity)
+        setSupportActionBar(findViewById(R.id.toolbar))
+        transition = TransitionInflater.from(this).inflateTransition(R.transition.app_bar)
+        appBar = findViewById(R.id.app_bar)
+        name = findViewById(R.id.name)
+        icon = findViewById(R.id.icon)
+        if (savedInstanceState == null) {
+            supportFragmentManager.transaction(now = true) {
+                replace(R.id.container, MainFragment())
+            }
+            intent?.let(::handleIntent)
+        }
+    }
+
+    override fun onNewIntent(intent: Intent?) {
+        super.onNewIntent(intent)
+        if (intent != null) {
+            handleIntent(intent)
+        }
+    }
+
+    private fun handleIntent(intent: Intent) {
+        if (intent.action == Intent.ACTION_VIEW) {
+            val id = intent.data.lastPathSegment.toLongOrNull()
+            if (id != null) {
+                openChat(id)
+            }
+        }
+    }
+
+    override fun updateAppBar(showContact: Boolean, hidden: Boolean, body: (name: TextView, icon: ImageView) -> Unit) {
+        if (hidden) {
+            appBar.visibility = View.GONE
+        } else {
+            appBar.visibility = View.VISIBLE
+            TransitionManager.beginDelayedTransition(appBar, transition)
+            if (showContact) {
+                supportActionBar?.setDisplayShowTitleEnabled(false)
+                name.visibility = View.VISIBLE
+                icon.visibility = View.VISIBLE
+            } else {
+                supportActionBar?.setDisplayShowTitleEnabled(true)
+                name.visibility = View.GONE
+                icon.visibility = View.GONE
+            }
+        }
+        body(name, icon)
+    }
+
+    override fun openChat(id: Long) {
+        supportFragmentManager.popBackStack(FRAGMENT_CHAT, FragmentManager.POP_BACK_STACK_INCLUSIVE)
+        supportFragmentManager.transaction {
+            addToBackStack(FRAGMENT_CHAT)
+            replace(R.id.container, ChatFragment.newInstance(id, true))
+        }
+    }
+
+    override fun openPhoto(photo: Int) {
+        supportFragmentManager.transaction {
+            addToBackStack(null)
+            replace(R.id.container, PhotoFragment.newInstance(photo))
+        }
+    }
+}
+
diff --git a/notification/Bubbles/app/src/main/java/com/example/android/bubbles/NavigationController.kt b/notification/Bubbles/app/src/main/java/com/example/android/bubbles/NavigationController.kt
new file mode 100644
index 0000000..c15c4dd
--- /dev/null
+++ b/notification/Bubbles/app/src/main/java/com/example/android/bubbles/NavigationController.kt
@@ -0,0 +1,46 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *       http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR 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.bubbles
+
+import android.widget.ImageView
+import android.widget.TextView
+import androidx.annotation.DrawableRes
+import androidx.fragment.app.Fragment
+
+/**
+ * Common interface between [MainActivity] and [BubbleActivity].
+ */
+interface NavigationController {
+
+    fun openChat(id: Long)
+
+    fun openPhoto(@DrawableRes photo: Int)
+
+    /**
+     * Updates the appearance and functionality of the app bar.
+     *
+     * @param showContact Whether to show contact information instead the screen title.
+     * @param hidden Whether to hide the app bar.
+     * @param body Provide this function to update the content of the app bar.
+     */
+    fun updateAppBar(
+        showContact: Boolean = true,
+        hidden: Boolean = false,
+        body: (name: TextView, icon: ImageView) -> Unit = { _, _ -> }
+    )
+}
+
+fun Fragment.getNavigationController() = requireActivity() as NavigationController
diff --git a/notification/Bubbles/app/src/main/java/com/example/android/bubbles/VoiceCallActivity.kt b/notification/Bubbles/app/src/main/java/com/example/android/bubbles/VoiceCallActivity.kt
new file mode 100644
index 0000000..4691769
--- /dev/null
+++ b/notification/Bubbles/app/src/main/java/com/example/android/bubbles/VoiceCallActivity.kt
@@ -0,0 +1,49 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *       http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR 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.bubbles
+
+import android.os.Bundle
+import android.widget.ImageView
+import android.widget.TextView
+import androidx.appcompat.app.AppCompatActivity
+import com.bumptech.glide.Glide
+import com.bumptech.glide.request.RequestOptions
+
+/**
+ * A dummy voice call screen. It only shows the icon and the name.
+ */
+class VoiceCallActivity : AppCompatActivity() {
+
+    companion object {
+        const val EXTRA_NAME = "name"
+        const val EXTRA_ICON = "icon"
+    }
+
+    override fun onCreate(savedInstanceState: Bundle?) {
+        super.onCreate(savedInstanceState)
+        setContentView(R.layout.voice_call_activity)
+        val name = intent.getStringExtra(EXTRA_NAME)
+        val icon = intent.getIntExtra(EXTRA_ICON, 0)
+        if (name == null || icon == 0) {
+            finish()
+            return
+        }
+        val textName: TextView = findViewById(R.id.name)
+        textName.text = name
+        val imageIcon: ImageView = findViewById(R.id.icon)
+        Glide.with(imageIcon).load(icon).apply(RequestOptions.circleCropTransform()).into(imageIcon)
+    }
+}
diff --git a/notification/Bubbles/app/src/main/java/com/example/android/bubbles/data/Chat.kt b/notification/Bubbles/app/src/main/java/com/example/android/bubbles/data/Chat.kt
new file mode 100644
index 0000000..0d0fc5f
--- /dev/null
+++ b/notification/Bubbles/app/src/main/java/com/example/android/bubbles/data/Chat.kt
@@ -0,0 +1,44 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *       http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR 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.bubbles.data
+
+typealias ChatThreadListener = (List<Message>) -> Unit
+
+class Chat(val contact: Contact) {
+
+    private val listeners = mutableListOf<ChatThreadListener>()
+
+    private val _messages = mutableListOf(
+        Message(1L, contact.id, "Send me a message", null, System.currentTimeMillis()),
+        Message(2L, contact.id, "I will reply in 5 seconds", null, System.currentTimeMillis())
+    )
+    val messages: List<Message>
+        get() = _messages
+
+    fun addListener(listener: ChatThreadListener) {
+        listeners.add(listener)
+    }
+
+    fun removeListener(listener: ChatThreadListener) {
+        listeners.remove(listener)
+    }
+
+    fun addMessage(builder: Message.Builder) {
+        builder.id = _messages.last().id + 1
+        _messages.add(builder.build())
+        listeners.forEach { listener -> listener(_messages) }
+    }
+}
diff --git a/notification/Bubbles/app/src/main/java/com/example/android/bubbles/data/ChatRepository.kt b/notification/Bubbles/app/src/main/java/com/example/android/bubbles/data/ChatRepository.kt
new file mode 100644
index 0000000..cf3af75
--- /dev/null
+++ b/notification/Bubbles/app/src/main/java/com/example/android/bubbles/data/ChatRepository.kt
@@ -0,0 +1,141 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *       http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR 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.bubbles.data
+
+import android.content.Context
+import androidx.annotation.MainThread
+import androidx.lifecycle.LiveData
+import androidx.lifecycle.MutableLiveData
+import java.util.concurrent.Executor
+import java.util.concurrent.Executors
+
+interface ChatRepository {
+    fun getContacts(): LiveData<List<Contact>>
+    fun findContact(id: Long): LiveData<Contact?>
+    fun findMessages(id: Long): LiveData<List<Message>>
+    fun sendMessage(id: Long, text: String)
+    fun activateChat(id: Long)
+    fun deactivateChat(id: Long)
+    fun showAsBubble(id: Long)
+    fun canBubble(): Boolean
+}
+
+class DefaultChatRepository internal constructor(
+    private val notificationHelper: NotificationHelper,
+    private val executor: Executor
+) : ChatRepository {
+
+    companion object {
+        private var instance: DefaultChatRepository? = null
+
+        fun getInstance(context: Context): DefaultChatRepository {
+            return instance ?: synchronized(this) {
+                instance ?: DefaultChatRepository(
+                    NotificationHelper(context),
+                    Executors.newFixedThreadPool(4)
+                ).also {
+                    instance = it
+                }
+            }
+        }
+    }
+
+    private var currentChat: Long = 0L
+
+    private val chats = Contact.CONTACTS.map { contact ->
+        contact.id to Chat(contact)
+    }.toMap()
+
+    init {
+        notificationHelper.setUpNotificationChannels()
+    }
+
+    @MainThread
+    override fun getContacts(): LiveData<List<Contact>> {
+        return MutableLiveData<List<Contact>>().apply {
+            postValue(Contact.CONTACTS)
+        }
+    }
+
+    @MainThread
+    override fun findContact(id: Long): LiveData<Contact?> {
+        return MutableLiveData<Contact>().apply {
+            postValue(Contact.CONTACTS.find { it.id == id })
+        }
+    }
+
+    @MainThread
+    override fun findMessages(id: Long): LiveData<List<Message>> {
+        val chat = chats.getValue(id)
+        return object : LiveData<List<Message>>() {
+
+            private val listener = { messages: List<Message> ->
+                postValue(messages)
+            }
+
+            override fun onActive() {
+                value = chat.messages
+                chat.addListener(listener)
+            }
+
+            override fun onInactive() {
+                chat.removeListener(listener)
+            }
+        }
+    }
+
+    @MainThread
+    override fun sendMessage(id: Long, text: String) {
+        val chat = chats.getValue(id)
+        chat.addMessage(Message.Builder().apply {
+            sender = 0L // User
+            this.text = text
+            timestamp = System.currentTimeMillis()
+        })
+        executor.execute {
+            // The animal is typing...
+            Thread.sleep(5000L)
+            // Receive a reply.
+            chat.addMessage(chat.contact.reply(text))
+            // Show notification if the chat is not on the foreground.
+            if (chat.contact.id != currentChat) {
+                notificationHelper.showNotification(chat, false)
+            }
+        }
+    }
+
+    override fun activateChat(id: Long) {
+        currentChat = id
+        notificationHelper.dismissNotification(id)
+    }
+
+    override fun deactivateChat(id: Long) {
+        if (currentChat == id) {
+            currentChat = 0
+        }
+    }
+
+    override fun showAsBubble(id: Long) {
+        val chat = chats.getValue(id)
+        executor.execute {
+            notificationHelper.showNotification(chat, true)
+        }
+    }
+
+    override fun canBubble(): Boolean {
+        return notificationHelper.canBubble()
+    }
+}
diff --git a/notification/Bubbles/app/src/main/java/com/example/android/bubbles/data/Contact.kt b/notification/Bubbles/app/src/main/java/com/example/android/bubbles/data/Contact.kt
new file mode 100644
index 0000000..28798f3
--- /dev/null
+++ b/notification/Bubbles/app/src/main/java/com/example/android/bubbles/data/Contact.kt
@@ -0,0 +1,74 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *       http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR 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.bubbles.data
+
+import androidx.annotation.DrawableRes
+import com.example.android.bubbles.R
+
+abstract class Contact(
+    val id: Long,
+    val name: String,
+    @DrawableRes
+    val icon: Int
+) {
+
+    companion object {
+        val CONTACTS = listOf(
+            object : Contact(1L, "Cat", R.drawable.cat) {
+                override fun reply(text: String) = buildReply().apply { this.text = "Meow" }
+            },
+            object : Contact(2L, "Dog", R.drawable.dog) {
+                override fun reply(text: String) = buildReply().apply { this.text = "Woof woof!!" }
+            },
+            object : Contact(3L, "Parrot", R.drawable.parrot) {
+                override fun reply(text: String) = buildReply().apply { this.text = text }
+            },
+            object : Contact(4L, "Sheep", R.drawable.sheep) {
+                override fun reply(text: String) = buildReply().apply {
+                    this.text = "Look at me!"
+                    photo = R.drawable.sheep_full
+                }
+            }
+        )
+    }
+
+    fun buildReply() = Message.Builder().apply {
+        sender = this@Contact.id
+        timestamp = System.currentTimeMillis()
+    }
+
+    abstract fun reply(text: String): Message.Builder
+
+    override fun equals(other: Any?): Boolean {
+        if (this === other) return true
+        if (javaClass != other?.javaClass) return false
+
+        other as Contact
+
+        if (id != other.id) return false
+        if (name != other.name) return false
+        if (icon != other.icon) return false
+
+        return true
+    }
+
+    override fun hashCode(): Int {
+        var result = id.hashCode()
+        result = 31 * result + name.hashCode()
+        result = 31 * result + icon
+        return result
+    }
+}
diff --git a/notification/Bubbles/app/src/main/java/com/example/android/bubbles/data/Message.kt b/notification/Bubbles/app/src/main/java/com/example/android/bubbles/data/Message.kt
new file mode 100644
index 0000000..bc48a0f
--- /dev/null
+++ b/notification/Bubbles/app/src/main/java/com/example/android/bubbles/data/Message.kt
@@ -0,0 +1,40 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *       http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR 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.bubbles.data
+
+import androidx.annotation.DrawableRes
+
+data class Message(
+    val id: Long,
+    val sender: Long,
+    val text: String,
+    @DrawableRes
+    val photo: Int?,
+    val timestamp: Long
+) {
+
+    val isIncoming: Boolean
+        get() = sender != 0L
+
+    class Builder {
+        var id: Long? = null
+        var sender: Long? = null
+        var text: String? = null
+        var photo: Int? = null
+        var timestamp: Long? = null
+        fun build() = Message(id!!, sender!!, text!!, photo, timestamp!!)
+    }
+}
diff --git a/notification/Bubbles/app/src/main/java/com/example/android/bubbles/data/NotificationHelper.kt b/notification/Bubbles/app/src/main/java/com/example/android/bubbles/data/NotificationHelper.kt
new file mode 100644
index 0000000..825489c
--- /dev/null
+++ b/notification/Bubbles/app/src/main/java/com/example/android/bubbles/data/NotificationHelper.kt
@@ -0,0 +1,195 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *       http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR 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.bubbles.data
+
+import android.app.Notification
+import android.app.NotificationChannel
+import android.app.NotificationManager
+import android.app.PendingIntent
+import android.app.Person
+import android.content.Context
+import android.content.Intent
+import android.graphics.Bitmap
+import android.graphics.BitmapFactory
+import android.graphics.BlendMode
+import android.graphics.Color
+import android.graphics.Paint
+import android.graphics.Rect
+import android.graphics.drawable.Icon
+import android.net.Uri
+import androidx.annotation.DrawableRes
+import androidx.annotation.WorkerThread
+import androidx.core.graphics.applyCanvas
+import com.example.android.bubbles.BubbleActivity
+import com.example.android.bubbles.MainActivity
+import com.example.android.bubbles.R
+
+/**
+ * Handles all operations related to [Notification].
+ */
+class NotificationHelper(private val context: Context) {
+
+    companion object {
+        /**
+         * The notification channel for messages. This is used for showing Bubbles.
+         */
+        private const val CHANNEL_NEW_MESSAGES = "new_messages"
+
+        private const val REQUEST_CONTENT = 1
+        private const val REQUEST_BUBBLE = 2
+    }
+
+    private val notificationManager = context.getSystemService(NotificationManager::class.java)
+
+    fun setUpNotificationChannels() {
+        if (notificationManager.getNotificationChannel(CHANNEL_NEW_MESSAGES) == null) {
+            notificationManager.createNotificationChannel(
+                NotificationChannel(
+                    CHANNEL_NEW_MESSAGES,
+                    context.getString(R.string.channel_new_messages),
+                    // The importance must be IMPORTANCE_HIGH to show Bubbles.
+                    NotificationManager.IMPORTANCE_HIGH
+                ).apply {
+                    description = context.getString(R.string.channel_new_messages_description)
+                }
+            )
+        }
+    }
+
+    @WorkerThread
+    fun showNotification(chat: Chat, fromUser: Boolean) {
+        val icon = Icon.createWithBitmap(roundIcon(context, chat.contact.icon))
+        val person = Person.Builder()
+            .setName(chat.contact.name)
+            .setIcon(icon)
+            .build()
+        val contentUri = Uri.parse("https://android.example.com/chat/${chat.contact.id}")
+        val builder = Notification.Builder(context, CHANNEL_NEW_MESSAGES)
+            // A notification can be shown as a bubble by calling setBubbleMetadata()
+            .setBubbleMetadata(
+                Notification.BubbleMetadata.Builder()
+                    // The height of the expanded bubble.
+                    .setDesiredHeight(context.resources.getDimensionPixelSize(R.dimen.bubble_height))
+                    // The icon of the bubble.
+                    // TODO: The icon is not displayed in Android Q Beta 2.
+                    .setIcon(icon)
+                    .apply {
+                        // When the bubble is explicitly opened by the user, we can show the bubble automatically
+                        // in the expanded state. This works only when the app is in the foreground.
+                        // TODO: This does not yet work in Android Q Beta 2.
+                        if (fromUser) {
+                            setAutoExpandBubble(true)
+                            setSuppressInitialNotification(true)
+                        }
+                    }
+                    // The Intent to be used for the expanded bubble.
+                    .setIntent(
+                        PendingIntent.getActivity(
+                            context,
+                            REQUEST_BUBBLE,
+                            // Launch BubbleActivity as the expanded bubble.
+                            Intent(context, BubbleActivity::class.java)
+                                .setAction(Intent.ACTION_VIEW)
+                                .setData(Uri.parse("https://android.example.com/chat/${chat.contact.id}")),
+                            PendingIntent.FLAG_UPDATE_CURRENT
+                        )
+                    )
+                    .build()
+            )
+            // The user can turn off the bubble in system settings. In that case, this notification is shown as a
+            // normal notification instead of a bubble. Make sure that this notification works as a normal notification
+            // as well.
+            .setContentTitle(chat.contact.name)
+            .setSmallIcon(R.drawable.ic_message)
+            .setCategory(Notification.CATEGORY_MESSAGE)
+            .addPerson(person)
+            .setShowWhen(true)
+            // The content Intent is used when the user clicks on the "Open Content" icon button on the expanded bubble,
+            // as well as when the fall-back notification is clicked.
+            .setContentIntent(
+                PendingIntent.getActivity(
+                    context,
+                    REQUEST_CONTENT,
+                    Intent(context, MainActivity::class.java)
+                        .setAction(Intent.ACTION_VIEW)
+                        .setData(contentUri),
+                    PendingIntent.FLAG_UPDATE_CURRENT
+                )
+            )
+
+        if (fromUser) {
+            // This is a Bubble explicitly opened by the user.
+            builder.setContentText(context.getString(R.string.chat_with_contact, chat.contact.name))
+        } else {
+            // Let's add some more content to the notification in case it falls back to a normal notification.
+            val lastOutgoingId = chat.messages.last { !it.isIncoming }.id
+            val newMessages = chat.messages.filter { message ->
+                message.id > lastOutgoingId
+            }
+            val lastMessage = newMessages.last()
+            builder
+                .setStyle(
+                    if (lastMessage.photo != null) {
+                        Notification.BigPictureStyle()
+                            .bigPicture(BitmapFactory.decodeResource(context.resources, lastMessage.photo))
+                            .bigLargeIcon(icon)
+                            .setSummaryText(lastMessage.text)
+                    } else {
+                        Notification.MessagingStyle(person)
+                            .apply {
+                                for (message in newMessages) {
+                                    addMessage(message.text, message.timestamp, person)
+                                }
+                            }
+                            .setGroupConversation(false)
+                    }
+                )
+                .setContentText(newMessages.joinToString("\n") { it.text })
+                .setWhen(newMessages.last().timestamp)
+        }
+
+        notificationManager.notify(chat.contact.id.toInt(), builder.build())
+    }
+
+    fun dismissNotification(id: Long) {
+        notificationManager.cancel(id.toInt())
+    }
+
+    fun canBubble(): Boolean {
+        val channel = notificationManager.getNotificationChannel(CHANNEL_NEW_MESSAGES)
+        return notificationManager.areBubblesAllowed() && channel.canBubble()
+    }
+}
+
+@WorkerThread
+private fun roundIcon(context: Context, @DrawableRes id: Int): Bitmap {
+    val original = BitmapFactory.decodeResource(context.resources, id)
+    val width = original.width
+    val height = original.height
+    val rect = Rect(0, 0, width, height)
+    val icon = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888)
+    val paint = Paint().apply {
+        isAntiAlias = true
+        color = Color.BLACK
+    }
+    icon.applyCanvas {
+        drawARGB(0, 0, 0, 0)
+        drawOval(0f, 0f, width.toFloat(), height.toFloat(), paint)
+        paint.blendMode = BlendMode.SRC_IN
+        drawBitmap(original, rect, rect, paint)
+    }
+    return icon
+}
diff --git a/notification/Bubbles/app/src/main/java/com/example/android/bubbles/ui/chat/ChatFragment.kt b/notification/Bubbles/app/src/main/java/com/example/android/bubbles/ui/chat/ChatFragment.kt
new file mode 100644
index 0000000..4bc2548
--- /dev/null
+++ b/notification/Bubbles/app/src/main/java/com/example/android/bubbles/ui/chat/ChatFragment.kt
@@ -0,0 +1,213 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *       http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR 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.bubbles.ui.chat
+
+import android.content.Intent
+import android.graphics.drawable.Drawable
+import android.os.Bundle
+import android.transition.TransitionInflater
+import android.view.LayoutInflater
+import android.view.Menu
+import android.view.MenuInflater
+import android.view.MenuItem
+import android.view.View
+import android.view.ViewGroup
+import android.view.inputmethod.EditorInfo
+import android.widget.EditText
+import android.widget.ImageButton
+import android.widget.Toast
+import androidx.fragment.app.Fragment
+import androidx.lifecycle.Observer
+import androidx.lifecycle.ViewModelProviders
+import androidx.recyclerview.widget.LinearLayoutManager
+import androidx.recyclerview.widget.RecyclerView
+import com.bumptech.glide.Glide
+import com.bumptech.glide.load.DataSource
+import com.bumptech.glide.load.engine.GlideException
+import com.bumptech.glide.request.RequestListener
+import com.bumptech.glide.request.RequestOptions
+import com.bumptech.glide.request.target.Target
+import com.example.android.bubbles.R
+import com.example.android.bubbles.VoiceCallActivity
+import com.example.android.bubbles.getNavigationController
+
+/**
+ * The chat screen. This is used in the full app (MainActivity) as well as in the expanded Bubble (BubbleActivity).
+ */
+class ChatFragment : Fragment() {
+
+    companion object {
+        private const val ARG_ID = "id"
+        private const val ARG_FOREGROUND = "foreground"
+
+        fun newInstance(id: Long, foreground: Boolean) = ChatFragment().apply {
+            arguments = Bundle().apply {
+                putLong(ARG_ID, id)
+                putBoolean(ARG_FOREGROUND, foreground)
+            }
+        }
+    }
+
+    private lateinit var viewModel: ChatViewModel
+    private lateinit var input: EditText
+
+    override fun onCreate(savedInstanceState: Bundle?) {
+        super.onCreate(savedInstanceState)
+        setHasOptionsMenu(true)
+        enterTransition = TransitionInflater.from(context).inflateTransition(R.transition.slide_bottom)
+    }
+
+    override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
+        return inflater.inflate(R.layout.chat_fragment, container, false)
+    }
+
+    private val startPostponedTransitionOnEnd = object : RequestListener<Drawable> {
+        override fun onLoadFailed(
+            e: GlideException?,
+            model: Any?,
+            target: Target<Drawable>?,
+            isFirstResource: Boolean
+        ): Boolean {
+            startPostponedEnterTransition()
+            return false
+        }
+
+        override fun onResourceReady(
+            resource: Drawable?,
+            model: Any?,
+            target: Target<Drawable>?,
+            dataSource: DataSource?,
+            isFirstResource: Boolean
+        ): Boolean {
+            startPostponedEnterTransition()
+            return false
+        }
+    }
+
+    override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
+        val id = arguments?.getLong(ARG_ID)
+        if (id == null) {
+            fragmentManager?.popBackStack()
+            return
+        }
+        val navigationController = getNavigationController()
+
+        viewModel = ViewModelProviders.of(this).get(ChatViewModel::class.java)
+        viewModel.setChatId(id)
+
+        val messages: RecyclerView = view.findViewById(R.id.messages)
+        val voiceCall: ImageButton = view.findViewById(R.id.voice_call)
+        input = view.findViewById(R.id.input)
+        val send: ImageButton = view.findViewById(R.id.send)
+
+        val messageAdapter = MessageAdapter(view.context) { photo ->
+            navigationController.openPhoto(photo)
+        }
+        val linearLayoutManager = LinearLayoutManager(view.context).apply {
+            stackFromEnd = true
+        }
+        messages.run {
+            layoutManager = linearLayoutManager
+            adapter = messageAdapter
+        }
+
+        viewModel.contact.observe(viewLifecycleOwner, Observer { chat ->
+            if (chat == null) {
+                Toast.makeText(view.context, "Contact not found", Toast.LENGTH_SHORT).show()
+                fragmentManager?.popBackStack()
+            } else {
+                navigationController.updateAppBar { name, icon ->
+                    name.text = chat.name
+                    Glide.with(icon)
+                        .load(chat.icon)
+                        .apply(RequestOptions.circleCropTransform())
+                        .dontAnimate()
+                        .addListener(startPostponedTransitionOnEnd)
+                        .into(icon)
+                }
+            }
+        })
+
+        viewModel.messages.observe(viewLifecycleOwner, Observer {
+            messageAdapter.submitList(it)
+            linearLayoutManager.scrollToPosition(it.size - 1)
+        })
+
+        voiceCall.setOnClickListener {
+            voiceCall()
+        }
+        send.setOnClickListener {
+            send()
+        }
+        input.setOnEditorActionListener { _, actionId, _ ->
+            if (actionId == EditorInfo.IME_ACTION_SEND) {
+                send()
+                true
+            } else {
+                false
+            }
+        }
+    }
+
+    override fun onStart() {
+        super.onStart()
+        val foreground = arguments?.getBoolean(ARG_FOREGROUND) == true
+        viewModel.foreground = foreground
+    }
+
+    override fun onStop() {
+        super.onStop()
+        viewModel.foreground = false
+    }
+
+    private fun voiceCall() {
+        val contact = viewModel.contact.value ?: return
+        startActivity(
+            Intent(requireActivity(), VoiceCallActivity::class.java)
+                .putExtra(VoiceCallActivity.EXTRA_NAME, contact.name)
+                .putExtra(VoiceCallActivity.EXTRA_ICON, contact.icon)
+        )
+    }
+
+    private fun send() {
+        val text = input.text.toString()
+        if (text.isNotEmpty()) {
+            input.text.clear()
+            viewModel.send(text)
+        }
+    }
+
+    override fun onCreateOptionsMenu(menu: Menu?, inflater: MenuInflater?) {
+        inflater?.inflate(R.menu.chat, menu)
+        menu?.findItem(R.id.action_show_as_bubble)?.let { item ->
+            viewModel.showAsBubbleVisible.observe(viewLifecycleOwner, Observer {
+                item.isVisible = it
+            })
+        }
+        super.onCreateOptionsMenu(menu, inflater)
+    }
+
+    override fun onOptionsItemSelected(item: MenuItem?): Boolean {
+        return when (item?.itemId) {
+            R.id.action_show_as_bubble -> {
+                viewModel.showAsBubble()
+                fragmentManager?.popBackStack()
+                true
+            }
+            else -> super.onOptionsItemSelected(item)
+        }
+    }
+}
diff --git a/notification/Bubbles/app/src/main/java/com/example/android/bubbles/ui/chat/ChatViewModel.kt b/notification/Bubbles/app/src/main/java/com/example/android/bubbles/ui/chat/ChatViewModel.kt
new file mode 100644
index 0000000..df8746c
--- /dev/null
+++ b/notification/Bubbles/app/src/main/java/com/example/android/bubbles/ui/chat/ChatViewModel.kt
@@ -0,0 +1,104 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *       http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR 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.bubbles.ui.chat
+
+import android.app.Application
+import androidx.lifecycle.AndroidViewModel
+import androidx.lifecycle.LiveData
+import androidx.lifecycle.MutableLiveData
+import androidx.lifecycle.Transformations
+import com.example.android.bubbles.data.ChatRepository
+import com.example.android.bubbles.data.Contact
+import com.example.android.bubbles.data.DefaultChatRepository
+import com.example.android.bubbles.data.Message
+
+class ChatViewModel @JvmOverloads constructor(
+    application: Application,
+    private val repository: ChatRepository = DefaultChatRepository.getInstance(application)
+) : AndroidViewModel(application) {
+
+    private val chatId = MutableLiveData<Long>()
+
+    /**
+     * We want to dismiss a notification when the corresponding chat screen is open. Setting this to `true` dismisses
+     * the current notification and suppresses further notifications.
+     *
+     * We do want to keep on showing and updating the notification when the chat screen is opened as an expanded bubble.
+     * [ChatFragment] should set this to false if it is launched in BubbleActivity. Otherwise, the expanding a bubble
+     * would remove the notification and the bubble.
+     */
+    var foreground = false
+        set(value) {
+            field = value
+            chatId.value?.let { id ->
+                if (value) {
+                    repository.activateChat(id)
+                } else {
+                    repository.deactivateChat(id)
+                }
+            }
+        }
+
+    /**
+     * The contact of this chat.
+     */
+    val contact: LiveData<Contact?> = Transformations.switchMap(chatId) { id ->
+        repository.findContact(id)
+    }
+
+    /**
+     * The list of all the messages in this chat.
+     */
+    val messages: LiveData<List<Message>> = Transformations.switchMap(chatId) { id ->
+        repository.findMessages(id)
+    }
+
+    /**
+     * Whether the "Show as Bubble" button should be shown.
+     */
+    val showAsBubbleVisible: LiveData<Boolean> = object: LiveData<Boolean>() {
+        override fun onActive() {
+            // We hide the "Show as Bubble" button if we are not allowed to show the bubble.
+            value = repository.canBubble()
+        }
+    }
+
+    fun setChatId(id: Long) {
+        chatId.value = id
+        if (foreground) {
+            repository.activateChat(id)
+        } else {
+            repository.deactivateChat(id)
+        }
+    }
+
+    fun send(text: String) {
+        val id = chatId.value
+        if (id != null && id != 0L) {
+            repository.sendMessage(id, text)
+        }
+    }
+
+    fun showAsBubble() {
+        chatId.value?.let { id ->
+            repository.showAsBubble(id)
+        }
+    }
+
+    override fun onCleared() {
+        chatId.value?.let { id -> repository.deactivateChat(id) }
+    }
+}
diff --git a/notification/Bubbles/app/src/main/java/com/example/android/bubbles/ui/chat/MessageAdapter.kt b/notification/Bubbles/app/src/main/java/com/example/android/bubbles/ui/chat/MessageAdapter.kt
new file mode 100644
index 0000000..9fd7242
--- /dev/null
+++ b/notification/Bubbles/app/src/main/java/com/example/android/bubbles/ui/chat/MessageAdapter.kt
@@ -0,0 +1,132 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *       http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR 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.bubbles.ui.chat
+
+import android.content.Context
+import android.content.res.ColorStateList
+import android.view.Gravity
+import android.view.LayoutInflater
+import android.view.ViewGroup
+import android.widget.FrameLayout
+import android.widget.TextView
+import androidx.core.content.ContextCompat
+import androidx.core.view.ViewCompat
+import androidx.recyclerview.widget.DiffUtil
+import androidx.recyclerview.widget.ListAdapter
+import androidx.recyclerview.widget.RecyclerView
+import com.example.android.bubbles.R
+import com.example.android.bubbles.data.Message
+
+class MessageAdapter(
+    context: Context,
+    private val onPhotoClicked: (photo: Int) -> Unit
+) : ListAdapter<Message, MessageViewHolder>(DIFF_CALLBACK) {
+
+    private val tint = object {
+        val incoming: ColorStateList = ColorStateList.valueOf(ContextCompat.getColor(context, R.color.incoming))
+        val outgoing: ColorStateList = ColorStateList.valueOf(
+            ContextCompat.getColor(context, R.color.outgoing)
+        )
+    }
+
+    private val padding = object {
+        val vertical: Int = context.resources.getDimensionPixelSize(R.dimen.message_padding_vertical)
+
+        val horizontalShort: Int = context.resources.getDimensionPixelSize(
+            R.dimen.message_padding_horizontal_short
+        )
+
+        val horizontalLong: Int = context.resources.getDimensionPixelSize(
+            R.dimen.message_padding_horizontal_long
+        )
+    }
+
+
+    init {
+        setHasStableIds(true)
+    }
+
+    override fun getItemId(position: Int): Long {
+        return getItem(position).id
+    }
+
+    override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): MessageViewHolder {
+        val holder = MessageViewHolder(parent)
+        holder.message.setOnClickListener {
+            val photo: Int? = it.getTag(R.id.tag_photo) as Int?
+            if (photo != null) {
+                onPhotoClicked(photo)
+            }
+        }
+        return holder
+    }
+
+    override fun onBindViewHolder(holder: MessageViewHolder, position: Int) {
+        val message = getItem(position)
+        val lp = holder.message.layoutParams as FrameLayout.LayoutParams
+        if (message.isIncoming) {
+            holder.message.run {
+                setBackgroundResource(R.drawable.message_incoming)
+                ViewCompat.setBackgroundTintList(this, tint.incoming)
+                setPadding(
+                    padding.horizontalLong, padding.vertical,
+                    padding.horizontalShort, padding.vertical
+                )
+                layoutParams = lp.apply {
+                    gravity = Gravity.START
+                }
+                if (message.photo != null) {
+                    holder.message.setTag(R.id.tag_photo, message.photo)
+                    setCompoundDrawablesWithIntrinsicBounds(0, 0, 0, message.photo)
+                } else {
+                    holder.message.setTag(R.id.tag_photo, null)
+                    setCompoundDrawables(null, null, null, null)
+                }
+            }
+        } else {
+            holder.message.run {
+                setBackgroundResource(R.drawable.message_outgoing)
+                ViewCompat.setBackgroundTintList(this, tint.outgoing)
+                setPadding(
+                    padding.horizontalShort, padding.vertical,
+                    padding.horizontalLong, padding.vertical
+                )
+                layoutParams = lp.apply {
+                    gravity = Gravity.END
+                }
+            }
+        }
+        holder.message.text = message.text
+    }
+}
+
+private val DIFF_CALLBACK = object : DiffUtil.ItemCallback<Message>() {
+
+    override fun areItemsTheSame(oldItem: Message, newItem: Message): Boolean {
+        return oldItem.id == newItem.id
+    }
+
+    override fun areContentsTheSame(oldItem: Message, newItem: Message): Boolean {
+        return oldItem == newItem
+    }
+
+}
+
+class MessageViewHolder(parent: ViewGroup) : RecyclerView.ViewHolder(
+    LayoutInflater.from(parent.context).inflate(R.layout.message_item, parent, false)
+) {
+    val message: TextView = itemView.findViewById(R.id.message)
+}
diff --git a/notification/Bubbles/app/src/main/java/com/example/android/bubbles/ui/main/ContactAdapter.kt b/notification/Bubbles/app/src/main/java/com/example/android/bubbles/ui/main/ContactAdapter.kt
new file mode 100644
index 0000000..4badf9b
--- /dev/null
+++ b/notification/Bubbles/app/src/main/java/com/example/android/bubbles/ui/main/ContactAdapter.kt
@@ -0,0 +1,72 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *       http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR 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.bubbles.ui.main
+
+import android.view.LayoutInflater
+import android.view.ViewGroup
+import android.widget.ImageView
+import android.widget.TextView
+import androidx.recyclerview.widget.DiffUtil
+import androidx.recyclerview.widget.ListAdapter
+import androidx.recyclerview.widget.RecyclerView
+import com.bumptech.glide.Glide
+import com.bumptech.glide.request.RequestOptions
+import com.example.android.bubbles.R
+import com.example.android.bubbles.data.Contact
+
+class ContactAdapter(
+    private val onChatClicked: (id: Long) -> Unit
+) : ListAdapter<Contact, ContactViewHolder>(DIFF_CALLBACK) {
+
+    init {
+        setHasStableIds(true)
+    }
+
+    override fun getItemId(position: Int): Long {
+        return getItem(position).id
+    }
+
+    override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ContactViewHolder {
+        val holder = ContactViewHolder(parent)
+        holder.itemView.setOnClickListener {
+            onChatClicked(holder.itemId)
+        }
+        return holder
+    }
+
+    override fun onBindViewHolder(holder: ContactViewHolder, position: Int) {
+        val contact: Contact = getItem(position)
+        Glide.with(holder.icon).load(contact.icon).apply(RequestOptions.circleCropTransform()).into(holder.icon)
+        holder.name.text = contact.name
+    }
+}
+
+private val DIFF_CALLBACK = object : DiffUtil.ItemCallback<Contact>() {
+    override fun areItemsTheSame(oldItem: Contact, newItem: Contact): Boolean {
+        return oldItem.id == newItem.id
+    }
+
+    override fun areContentsTheSame(oldItem: Contact, newItem: Contact): Boolean {
+        return oldItem == newItem
+    }
+}
+
+class ContactViewHolder(parent: ViewGroup) : RecyclerView.ViewHolder(
+    LayoutInflater.from(parent.context).inflate(R.layout.chat_item, parent, false)
+) {
+    val icon: ImageView = itemView.findViewById(R.id.icon)
+    val name: TextView = itemView.findViewById(R.id.name)
+}
diff --git a/notification/Bubbles/app/src/main/java/com/example/android/bubbles/ui/main/MainFragment.kt b/notification/Bubbles/app/src/main/java/com/example/android/bubbles/ui/main/MainFragment.kt
new file mode 100644
index 0000000..ee4b920
--- /dev/null
+++ b/notification/Bubbles/app/src/main/java/com/example/android/bubbles/ui/main/MainFragment.kt
@@ -0,0 +1,63 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *       http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR 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.bubbles.ui.main
+
+import android.os.Bundle
+import android.transition.TransitionInflater
+import android.view.LayoutInflater
+import android.view.View
+import android.view.ViewGroup
+import androidx.fragment.app.Fragment
+import androidx.lifecycle.Observer
+import androidx.lifecycle.ViewModelProviders
+import androidx.recyclerview.widget.LinearLayoutManager
+import androidx.recyclerview.widget.RecyclerView
+import com.example.android.bubbles.R
+import com.example.android.bubbles.getNavigationController
+
+/**
+ * The main chat list screen.
+ */
+class MainFragment : Fragment() {
+
+    override fun onCreate(savedInstanceState: Bundle?) {
+        super.onCreate(savedInstanceState)
+        exitTransition = TransitionInflater.from(context).inflateTransition(R.transition.slide_top)
+    }
+
+    override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
+        return inflater.inflate(R.layout.main_fragment, container, false)
+    }
+
+    override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
+        val navigationController = getNavigationController()
+        navigationController.updateAppBar(false)
+        val viewModel = ViewModelProviders.of(this).get(MainViewModel::class.java)
+
+        val contactAdapter = ContactAdapter { id ->
+            navigationController.openChat(id)
+        }
+        viewModel.contacts.observe(viewLifecycleOwner, Observer { contacts ->
+            contactAdapter.submitList(contacts)
+        })
+
+        view.findViewById<RecyclerView>(R.id.contacts).run {
+            layoutManager = LinearLayoutManager(view.context)
+            setHasFixedSize(true)
+            adapter = contactAdapter
+        }
+    }
+}
diff --git a/notification/Bubbles/app/src/main/java/com/example/android/bubbles/ui/main/MainViewModel.kt b/notification/Bubbles/app/src/main/java/com/example/android/bubbles/ui/main/MainViewModel.kt
new file mode 100644
index 0000000..f959352
--- /dev/null
+++ b/notification/Bubbles/app/src/main/java/com/example/android/bubbles/ui/main/MainViewModel.kt
@@ -0,0 +1,32 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *       http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR 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.bubbles.ui.main
+
+import android.app.Application
+import androidx.lifecycle.AndroidViewModel
+import com.example.android.bubbles.data.ChatRepository
+import com.example.android.bubbles.data.DefaultChatRepository
+
+class MainViewModel @JvmOverloads constructor(
+    application: Application,
+    repository: ChatRepository = DefaultChatRepository.getInstance(application)
+) : AndroidViewModel(application) {
+
+    /**
+     * All the contacts.
+     */
+    val contacts = repository.getContacts()
+}
diff --git a/notification/Bubbles/app/src/main/java/com/example/android/bubbles/ui/photo/PhotoFragment.kt b/notification/Bubbles/app/src/main/java/com/example/android/bubbles/ui/photo/PhotoFragment.kt
new file mode 100644
index 0000000..14f6594
--- /dev/null
+++ b/notification/Bubbles/app/src/main/java/com/example/android/bubbles/ui/photo/PhotoFragment.kt
@@ -0,0 +1,47 @@
+package com.example.android.bubbles.ui.photo
+
+import android.os.Bundle
+import android.transition.Fade
+import android.view.LayoutInflater
+import android.view.View
+import android.view.ViewGroup
+import android.widget.ImageView
+import androidx.annotation.DrawableRes
+import androidx.fragment.app.Fragment
+import com.example.android.bubbles.R
+import com.example.android.bubbles.getNavigationController
+
+/**
+ * Shows the specified [DrawableRes] as a full-screen photo.
+ */
+class PhotoFragment : Fragment() {
+
+    companion object {
+        private const val ARG_PHOTO = "photo"
+
+        fun newInstance(@DrawableRes photo: Int) = PhotoFragment().apply {
+            arguments = Bundle().apply {
+                putInt(ARG_PHOTO, photo)
+            }
+        }
+    }
+
+    override fun onCreate(savedInstanceState: Bundle?) {
+        super.onCreate(savedInstanceState)
+        enterTransition = Fade()
+    }
+
+    override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
+        return inflater.inflate(R.layout.photo_fragment, container, false)
+    }
+
+    override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
+        val photoResId = arguments?.getInt(ARG_PHOTO)
+        if (photoResId == null) {
+            fragmentManager?.popBackStack()
+            return
+        }
+        getNavigationController().updateAppBar(hidden = true)
+        view.findViewById<ImageView>(R.id.photo).setImageResource(photoResId)
+    }
+}
diff --git a/notification/Bubbles/app/src/main/res/drawable-nodpi/cat.jpg b/notification/Bubbles/app/src/main/res/drawable-nodpi/cat.jpg
new file mode 100644
index 0000000..6196170
--- /dev/null
+++ b/notification/Bubbles/app/src/main/res/drawable-nodpi/cat.jpg
Binary files differ
diff --git a/notification/Bubbles/app/src/main/res/drawable-nodpi/dog.jpg b/notification/Bubbles/app/src/main/res/drawable-nodpi/dog.jpg
new file mode 100644
index 0000000..df15900
--- /dev/null
+++ b/notification/Bubbles/app/src/main/res/drawable-nodpi/dog.jpg
Binary files differ
diff --git a/notification/Bubbles/app/src/main/res/drawable-nodpi/parrot.jpg b/notification/Bubbles/app/src/main/res/drawable-nodpi/parrot.jpg
new file mode 100644
index 0000000..8e2e18d
--- /dev/null
+++ b/notification/Bubbles/app/src/main/res/drawable-nodpi/parrot.jpg
Binary files differ
diff --git a/notification/Bubbles/app/src/main/res/drawable-nodpi/sheep.jpg b/notification/Bubbles/app/src/main/res/drawable-nodpi/sheep.jpg
new file mode 100644
index 0000000..39876f9
--- /dev/null
+++ b/notification/Bubbles/app/src/main/res/drawable-nodpi/sheep.jpg
Binary files differ
diff --git a/notification/Bubbles/app/src/main/res/drawable-nodpi/sheep_full.jpg b/notification/Bubbles/app/src/main/res/drawable-nodpi/sheep_full.jpg
new file mode 100644
index 0000000..ad5e8c1
--- /dev/null
+++ b/notification/Bubbles/app/src/main/res/drawable-nodpi/sheep_full.jpg
Binary files differ
diff --git a/notification/Bubbles/app/src/main/res/drawable/ic_launcher_foreground.xml b/notification/Bubbles/app/src/main/res/drawable/ic_launcher_foreground.xml
new file mode 100644
index 0000000..f7cf56d
--- /dev/null
+++ b/notification/Bubbles/app/src/main/res/drawable/ic_launcher_foreground.xml
@@ -0,0 +1,18 @@
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+    android:width="108dp"
+    android:height="108dp"
+    android:viewportWidth="55.644802"
+    android:viewportHeight="55.644802">
+  <group android:translateX="3.4717517"
+      android:translateY="3.4717517">
+    <group android:translateX="-1.2175325"
+        android:translateY="-1.2175325">
+      <group android:translateX="13.568182"
+          android:translateY="13.568182">
+          <path
+              android:fillColor="#FFFFFFFF"
+              android:pathData="M20,2L4,2c-1.1,0 -1.99,0.9 -1.99,2L2,22l4,-4h14c1.1,0 2,-0.9 2,-2L22,4c0,-1.1 -0.9,-2 -2,-2zM18,14L6,14v-2h12v2zM18,11L6,11L6,9h12v2zM18,8L6,8L6,6h12v2z"/>
+      </group>
+    </group>
+  </group>
+</vector>
diff --git a/notification/Bubbles/app/src/main/res/drawable/ic_message.xml b/notification/Bubbles/app/src/main/res/drawable/ic_message.xml
new file mode 100644
index 0000000..d2876bf
--- /dev/null
+++ b/notification/Bubbles/app/src/main/res/drawable/ic_message.xml
@@ -0,0 +1,9 @@
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+        android:width="24dp"
+        android:height="24dp"
+        android:viewportWidth="24.0"
+        android:viewportHeight="24.0">
+    <path
+        android:fillColor="#FF000000"
+        android:pathData="M20,2L4,2c-1.1,0 -1.99,0.9 -1.99,2L2,22l4,-4h14c1.1,0 2,-0.9 2,-2L22,4c0,-1.1 -0.9,-2 -2,-2zM18,14L6,14v-2h12v2zM18,11L6,11L6,9h12v2zM18,8L6,8L6,6h12v2z"/>
+</vector>
diff --git a/notification/Bubbles/app/src/main/res/drawable/ic_open_in_new.xml b/notification/Bubbles/app/src/main/res/drawable/ic_open_in_new.xml
new file mode 100644
index 0000000..bd52826
--- /dev/null
+++ b/notification/Bubbles/app/src/main/res/drawable/ic_open_in_new.xml
@@ -0,0 +1,10 @@
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+    android:width="24dp"
+    android:height="24dp"
+    android:tint="#FFFFFF"
+    android:viewportWidth="24.0"
+    android:viewportHeight="24.0">
+    <path
+        android:fillColor="#FFFFFF"
+        android:pathData="M19,19H5V5h7V3H5c-1.11,0 -2,0.9 -2,2v14c0,1.1 0.89,2 2,2h14c1.1,0 2,-0.9 2,-2v-7h-2v7zM14,3v2h3.59l-9.83,9.83 1.41,1.41L19,6.41V10h2V3h-7z" />
+</vector>
diff --git a/notification/Bubbles/app/src/main/res/drawable/ic_send.xml b/notification/Bubbles/app/src/main/res/drawable/ic_send.xml
new file mode 100644
index 0000000..e145ca8
--- /dev/null
+++ b/notification/Bubbles/app/src/main/res/drawable/ic_send.xml
@@ -0,0 +1,9 @@
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+        android:width="24dp"
+        android:height="24dp"
+        android:viewportWidth="24.0"
+        android:viewportHeight="24.0">
+    <path
+        android:fillColor="#FF000000"
+        android:pathData="M2.01,21L23,12 2.01,3 2,10l15,2 -15,2z"/>
+</vector>
diff --git a/notification/Bubbles/app/src/main/res/drawable/ic_voice_call.xml b/notification/Bubbles/app/src/main/res/drawable/ic_voice_call.xml
new file mode 100644
index 0000000..ebf9de6
--- /dev/null
+++ b/notification/Bubbles/app/src/main/res/drawable/ic_voice_call.xml
@@ -0,0 +1,9 @@
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+        android:width="24dp"
+        android:height="24dp"
+        android:viewportWidth="24.0"
+        android:viewportHeight="24.0">
+    <path
+        android:fillColor="#FF000000"
+        android:pathData="M6.62,10.79c1.44,2.83 3.76,5.14 6.59,6.59l2.2,-2.2c0.27,-0.27 0.67,-0.36 1.02,-0.24 1.12,0.37 2.33,0.57 3.57,0.57 0.55,0 1,0.45 1,1V20c0,0.55 -0.45,1 -1,1 -9.39,0 -17,-7.61 -17,-17 0,-0.55 0.45,-1 1,-1h3.5c0.55,0 1,0.45 1,1 0,1.25 0.2,2.45 0.57,3.57 0.11,0.35 0.03,0.74 -0.25,1.02l-2.2,2.2z"/>
+</vector>
diff --git a/notification/Bubbles/app/src/main/res/drawable/message_incoming.xml b/notification/Bubbles/app/src/main/res/drawable/message_incoming.xml
new file mode 100644
index 0000000..94871b4
--- /dev/null
+++ b/notification/Bubbles/app/src/main/res/drawable/message_incoming.xml
@@ -0,0 +1,36 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  Copyright 2019 Google Inc. All Rights Reserved.
+
+  Licensed under the Apache License, Version 2.0 (the "License");
+  you may not use this file except in compliance with the License.
+  You may obtain a copy of the License at
+
+  http://www.apache.org/licenses/LICENSE-2.0
+
+  Unless required by applicable law or agreed to in writing, software
+  distributed under the License is distributed on an "AS IS" BASIS,
+  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  See the License for the specific language governing permissions and
+  limitations under the License.
+-->
+<layer-list
+    xmlns:android="http://schemas.android.com/apk/res/android">
+    <item>
+        <rotate
+            android:fromDegrees="-45"
+            android:pivotX="0%"
+            android:pivotY="0%"
+            android:toDegrees="0">
+            <shape android:shape="rectangle">
+                <solid android:color="#fff" />
+            </shape>
+        </rotate>
+    </item>
+    <item android:left="8dp">
+        <shape android:shape="rectangle">
+            <solid android:color="#fff" />
+            <corners android:radius="4dp" />
+        </shape>
+    </item>
+</layer-list>
diff --git a/notification/Bubbles/app/src/main/res/drawable/message_outgoing.xml b/notification/Bubbles/app/src/main/res/drawable/message_outgoing.xml
new file mode 100644
index 0000000..39cc7b1
--- /dev/null
+++ b/notification/Bubbles/app/src/main/res/drawable/message_outgoing.xml
@@ -0,0 +1,36 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  Copyright 2017 Google Inc. All Rights Reserved.
+
+  Licensed under the Apache License, Version 2.0 (the "License");
+  you may not use this file except in compliance with the License.
+  You may obtain a copy of the License at
+
+  http://www.apache.org/licenses/LICENSE-2.0
+
+  Unless required by applicable law or agreed to in writing, software
+  distributed under the License is distributed on an "AS IS" BASIS,
+  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  See the License for the specific language governing permissions and
+  limitations under the License.
+-->
+<layer-list
+    xmlns:android="http://schemas.android.com/apk/res/android">
+    <item>
+        <rotate
+            android:fromDegrees="45"
+            android:pivotX="100%"
+            android:pivotY="0%"
+            android:toDegrees="0">
+            <shape android:shape="rectangle">
+                <solid android:color="#fff" />
+            </shape>
+        </rotate>
+    </item>
+    <item android:right="8dp">
+        <shape android:shape="rectangle">
+            <solid android:color="#fff" />
+            <corners android:radius="4dp" />
+        </shape>
+    </item>
+</layer-list>
diff --git a/notification/Bubbles/app/src/main/res/layout/bubble_activity.xml b/notification/Bubbles/app/src/main/res/layout/bubble_activity.xml
new file mode 100644
index 0000000..6a766a0
--- /dev/null
+++ b/notification/Bubbles/app/src/main/res/layout/bubble_activity.xml
@@ -0,0 +1,21 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  Copyright (C) 2019 The Android Open Source Project
+
+  Licensed under the Apache License, Version 2.0 (the "License");
+  you may not use this file except in compliance with the License.
+  You may obtain a copy of the License at
+
+       http://www.apache.org/licenses/LICENSE-2.0
+
+  Unless required by applicable law or agreed to in writing, software
+  distributed under the License is distributed on an "AS IS" BASIS,
+  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  See the License for the specific language governing permissions and
+  limitations under the License.
+-->
+<FrameLayout
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    android:id="@+id/container"
+    android:layout_width="match_parent"
+    android:layout_height="match_parent" />
diff --git a/notification/Bubbles/app/src/main/res/layout/chat_fragment.xml b/notification/Bubbles/app/src/main/res/layout/chat_fragment.xml
new file mode 100644
index 0000000..4ce86e4
--- /dev/null
+++ b/notification/Bubbles/app/src/main/res/layout/chat_fragment.xml
@@ -0,0 +1,74 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  Copyright (C) 2019 The Android Open Source Project
+
+  Licensed under the Apache License, Version 2.0 (the "License");
+  you may not use this file except in compliance with the License.
+  You may obtain a copy of the License at
+
+       http://www.apache.org/licenses/LICENSE-2.0
+
+  Unless required by applicable law or agreed to in writing, software
+  distributed under the License is distributed on an "AS IS" BASIS,
+  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  See the License for the specific language governing permissions and
+  limitations under the License.
+-->
+<LinearLayout
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:app="http://schemas.android.com/apk/res-auto"
+    android:id="@+id/chat"
+    android:layout_width="match_parent"
+    android:layout_height="match_parent"
+    android:orientation="vertical">
+
+    <androidx.recyclerview.widget.RecyclerView
+        android:id="@+id/messages"
+        style="?attr/buttonBarStyle"
+        android:layout_width="match_parent"
+        android:layout_height="0dp"
+        android:layout_weight="1"
+        android:clipToPadding="false"
+        android:paddingTop="@dimen/spacing_small"
+        android:paddingBottom="@dimen/spacing_small"
+        android:scrollbars="vertical" />
+
+    <LinearLayout
+        android:id="@+id/input_bar"
+        android:layout_width="match_parent"
+        android:layout_height="?attr/actionBarSize"
+        android:background="?android:attr/windowBackground"
+        android:elevation="@dimen/app_bar_elevation"
+        android:orientation="horizontal">
+
+        <ImageButton
+            android:id="@+id/voice_call"
+            style="?attr/buttonBarNeutralButtonStyle"
+            android:layout_width="wrap_content"
+            android:layout_height="match_parent"
+            android:contentDescription="@string/description_voice_call"
+            android:tint="?attr/colorAccent"
+            app:srcCompat="@drawable/ic_voice_call" />
+
+        <EditText
+            android:id="@+id/input"
+            android:layout_width="0dp"
+            android:layout_height="match_parent"
+            android:layout_weight="1"
+            android:hint="@string/hint_input"
+            android:imeOptions="actionSend"
+            android:importantForAutofill="no"
+            android:inputType="textCapSentences" />
+
+        <ImageButton
+            android:id="@+id/send"
+            style="?attr/buttonBarNeutralButtonStyle"
+            android:layout_width="wrap_content"
+            android:layout_height="match_parent"
+            android:contentDescription="@string/description_send"
+            android:tint="?attr/colorAccent"
+            app:srcCompat="@drawable/ic_send" />
+
+    </LinearLayout>
+
+</LinearLayout>
diff --git a/notification/Bubbles/app/src/main/res/layout/chat_item.xml b/notification/Bubbles/app/src/main/res/layout/chat_item.xml
new file mode 100644
index 0000000..6f2bafc
--- /dev/null
+++ b/notification/Bubbles/app/src/main/res/layout/chat_item.xml
@@ -0,0 +1,50 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  Copyright (C) 2019 The Android Open Source Project
+
+  Licensed under the Apache License, Version 2.0 (the "License");
+  you may not use this file except in compliance with the License.
+  You may obtain a copy of the License at
+
+       http://www.apache.org/licenses/LICENSE-2.0
+
+  Unless required by applicable law or agreed to in writing, software
+  distributed under the License is distributed on an "AS IS" BASIS,
+  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  See the License for the specific language governing permissions and
+  limitations under the License.
+-->
+<LinearLayout
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:tools="http://schemas.android.com/tools"
+    android:id="@+id/chat"
+    android:layout_width="match_parent"
+    android:layout_height="wrap_content"
+    android:background="?attr/selectableItemBackground"
+    android:minHeight="?attr/listPreferredItemHeightLarge"
+    android:orientation="horizontal"
+    android:paddingStart="@dimen/spacing_small"
+    android:paddingEnd="@dimen/spacing_small"
+    tools:ignore="UseCompoundDrawables">
+
+    <ImageView
+        android:id="@+id/icon"
+        android:layout_width="64dp"
+        android:layout_height="64dp"
+        android:layout_gravity="center_vertical"
+        android:layout_margin="@dimen/spacing_small"
+        android:contentDescription="@string/description_icon"
+        tools:src="@drawable/cat" />
+
+    <TextView
+        android:id="@+id/name"
+        android:layout_width="0dp"
+        android:layout_height="wrap_content"
+        android:layout_gravity="center_vertical"
+        android:layout_margin="@dimen/spacing_small"
+        android:layout_weight="1"
+        android:maxLines="1"
+        android:textAppearance="@style/TextAppearance.AppCompat.Large"
+        tools:text="Cat" />
+
+</LinearLayout>
diff --git a/notification/Bubbles/app/src/main/res/layout/main_activity.xml b/notification/Bubbles/app/src/main/res/layout/main_activity.xml
new file mode 100644
index 0000000..df2a66e
--- /dev/null
+++ b/notification/Bubbles/app/src/main/res/layout/main_activity.xml
@@ -0,0 +1,77 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  Copyright (C) 2019 The Android Open Source Project
+
+  Licensed under the Apache License, Version 2.0 (the "License");
+  you may not use this file except in compliance with the License.
+  You may obtain a copy of the License at
+
+       http://www.apache.org/licenses/LICENSE-2.0
+
+  Unless required by applicable law or agreed to in writing, software
+  distributed under the License is distributed on an "AS IS" BASIS,
+  WITHOUT 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:id="@+id/root"
+    android:layout_width="match_parent"
+    android:layout_height="match_parent"
+    android:orientation="vertical"
+    tools:context=".MainActivity">
+
+    <androidx.constraintlayout.widget.ConstraintLayout
+        android:id="@+id/app_bar"
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        android:background="?attr/colorPrimary"
+        android:elevation="@dimen/app_bar_elevation"
+        android:theme="@style/ThemeOverlay.AppCompat.Dark">
+
+        <ImageView
+            android:id="@+id/icon"
+            android:layout_width="@dimen/icon_size"
+            android:layout_height="@dimen/icon_size"
+            android:layout_marginVertical="@dimen/spacing_small"
+            android:layout_marginStart="@dimen/spacing_medium"
+            android:contentDescription="@string/description_icon"
+            app:layout_constraintBottom_toBottomOf="parent"
+            app:layout_constraintStart_toStartOf="parent"
+            app:layout_constraintTop_toTopOf="parent"
+            tools:src="@drawable/cat" />
+
+        <TextView
+            android:id="@+id/name"
+            android:layout_width="0dp"
+            android:layout_height="0dp"
+            android:layout_marginStart="@dimen/spacing_medium"
+            android:gravity="center_vertical"
+            android:maxLines="1"
+            android:textAppearance="@style/TextAppearance.AppCompat.Large"
+            app:layout_constraintBottom_toBottomOf="@id/icon"
+            app:layout_constraintEnd_toEndOf="parent"
+            app:layout_constraintStart_toEndOf="@id/icon"
+            app:layout_constraintTop_toTopOf="@id/icon"
+            tools:text="Cat" />
+
+        <androidx.appcompat.widget.Toolbar
+            android:id="@+id/toolbar"
+            android:layout_width="0dp"
+            android:layout_height="match_parent"
+            app:layout_constraintEnd_toEndOf="parent"
+            app:layout_constraintStart_toStartOf="parent"
+            app:layout_constraintTop_toTopOf="parent" />
+
+    </androidx.constraintlayout.widget.ConstraintLayout>
+
+    <FrameLayout
+        android:id="@+id/container"
+        android:layout_width="match_parent"
+        android:layout_height="0dp"
+        android:layout_weight="1" />
+
+</LinearLayout>
diff --git a/notification/Bubbles/app/src/main/res/layout/main_fragment.xml b/notification/Bubbles/app/src/main/res/layout/main_fragment.xml
new file mode 100644
index 0000000..36a89bc
--- /dev/null
+++ b/notification/Bubbles/app/src/main/res/layout/main_fragment.xml
@@ -0,0 +1,24 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  Copyright (C) 2019 The Android Open Source Project
+
+  Licensed under the Apache License, Version 2.0 (the "License");
+  you may not use this file except in compliance with the License.
+  You may obtain a copy of the License at
+
+       http://www.apache.org/licenses/LICENSE-2.0
+
+  Unless required by applicable law or agreed to in writing, software
+  distributed under the License is distributed on an "AS IS" BASIS,
+  WITHOUT 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.recyclerview.widget.RecyclerView
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    android:id="@+id/contacts"
+    android:layout_width="match_parent"
+    android:layout_height="match_parent"
+    android:clipToPadding="false"
+    android:paddingTop="@dimen/spacing_small"
+    android:paddingBottom="@dimen/spacing_small" />
diff --git a/notification/Bubbles/app/src/main/res/layout/message_item.xml b/notification/Bubbles/app/src/main/res/layout/message_item.xml
new file mode 100644
index 0000000..b162a94
--- /dev/null
+++ b/notification/Bubbles/app/src/main/res/layout/message_item.xml
@@ -0,0 +1,28 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  Copyright (C) 2019 The Android Open Source Project
+
+  Licensed under the Apache License, Version 2.0 (the "License");
+  you may not use this file except in compliance with the License.
+  You may obtain a copy of the License at
+
+       http://www.apache.org/licenses/LICENSE-2.0
+
+  Unless required by applicable law or agreed to in writing, software
+  distributed under the License is distributed on an "AS IS" BASIS,
+  WITHOUT 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="wrap_content">
+
+    <TextView
+        android:id="@+id/message"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:layout_margin="@dimen/spacing_small" />
+
+</FrameLayout>
diff --git a/notification/Bubbles/app/src/main/res/layout/photo_fragment.xml b/notification/Bubbles/app/src/main/res/layout/photo_fragment.xml
new file mode 100644
index 0000000..15b63d3
--- /dev/null
+++ b/notification/Bubbles/app/src/main/res/layout/photo_fragment.xml
@@ -0,0 +1,26 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  Copyright (C) 2019 The Android Open Source Project
+
+  Licensed under the Apache License, Version 2.0 (the "License");
+  you may not use this file except in compliance with the License.
+  You may obtain a copy of the License at
+
+       http://www.apache.org/licenses/LICENSE-2.0
+
+  Unless required by applicable law or agreed to in writing, software
+  distributed under the License is distributed on an "AS IS" BASIS,
+  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  See the License for the specific language governing permissions and
+  limitations under the License.
+-->
+<ImageView
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:tools="http://schemas.android.com/tools"
+    android:id="@+id/photo"
+    android:layout_width="match_parent"
+    android:layout_height="match_parent"
+    android:background="@android:color/black"
+    android:scaleType="fitCenter"
+    tools:src="@drawable/sheep_full"
+    android:contentDescription="@string/description_photo" />
diff --git a/notification/Bubbles/app/src/main/res/layout/voice_call_activity.xml b/notification/Bubbles/app/src/main/res/layout/voice_call_activity.xml
new file mode 100644
index 0000000..9326eb7
--- /dev/null
+++ b/notification/Bubbles/app/src/main/res/layout/voice_call_activity.xml
@@ -0,0 +1,63 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  Copyright (C) 2019 The Android Open Source Project
+
+  Licensed under the Apache License, Version 2.0 (the "License");
+  you may not use this file except in compliance with the License.
+  You may obtain a copy of the License at
+
+       http://www.apache.org/licenses/LICENSE-2.0
+
+  Unless required by applicable law or agreed to in writing, software
+  distributed under the License is distributed on an "AS IS" BASIS,
+  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  See the License for the specific language governing permissions and
+  limitations under the License.
+-->
+<LinearLayout
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:tools="http://schemas.android.com/tools"
+    android:id="@+id/voice"
+    android:layout_width="match_parent"
+    android:layout_height="match_parent"
+    android:orientation="vertical"
+    tools:context=".VoiceCallActivity">
+
+    <Space
+        android:layout_width="0dp"
+        android:layout_height="0dp"
+        android:layout_weight="1" />
+
+    <ImageView
+        android:id="@+id/icon"
+        android:layout_width="@dimen/icon_size"
+        android:layout_height="@dimen/icon_size"
+        android:layout_gravity="center_horizontal"
+        android:layout_margin="@dimen/spacing_medium"
+        android:contentDescription="@string/description_icon"
+        tools:src="@drawable/cat" />
+
+    <TextView
+        android:id="@+id/name"
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        android:layout_margin="@dimen/spacing_medium"
+        android:gravity="center_horizontal"
+        android:textAppearance="@style/TextAppearance.AppCompat.Large"
+        tools:text="Cat" />
+
+    <TextView
+        android:id="@+id/explanation"
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        android:layout_margin="@dimen/spacing_medium"
+        android:gravity="center_horizontal"
+        android:text="@string/voice_call_explanation"
+        android:textAppearance="@style/TextAppearance.AppCompat.Medium" />
+
+    <Space
+        android:layout_width="0dp"
+        android:layout_height="0dp"
+        android:layout_weight="1" />
+
+</LinearLayout>
diff --git a/notification/Bubbles/app/src/main/res/menu/chat.xml b/notification/Bubbles/app/src/main/res/menu/chat.xml
new file mode 100644
index 0000000..f7b1269
--- /dev/null
+++ b/notification/Bubbles/app/src/main/res/menu/chat.xml
@@ -0,0 +1,11 @@
+<?xml version="1.0" encoding="utf-8"?>
+<menu xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:app="http://schemas.android.com/apk/res-auto">
+
+    <item
+        android:id="@+id/action_show_as_bubble"
+        android:icon="@drawable/ic_open_in_new"
+        android:title="@string/show_as_bubble"
+        app:showAsAction="ifRoom" />
+
+</menu>
diff --git a/notification/Bubbles/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml b/notification/Bubbles/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml
new file mode 100644
index 0000000..7353dbd
--- /dev/null
+++ b/notification/Bubbles/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml
@@ -0,0 +1,5 @@
+<?xml version="1.0" encoding="utf-8"?>
+<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
+    <background android:drawable="@color/ic_launcher_background"/>
+    <foreground android:drawable="@drawable/ic_launcher_foreground"/>
+</adaptive-icon>
\ No newline at end of file
diff --git a/notification/Bubbles/app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml b/notification/Bubbles/app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml
new file mode 100644
index 0000000..7353dbd
--- /dev/null
+++ b/notification/Bubbles/app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml
@@ -0,0 +1,5 @@
+<?xml version="1.0" encoding="utf-8"?>
+<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
+    <background android:drawable="@color/ic_launcher_background"/>
+    <foreground android:drawable="@drawable/ic_launcher_foreground"/>
+</adaptive-icon>
\ No newline at end of file
diff --git a/notification/Bubbles/app/src/main/res/mipmap-hdpi/ic_launcher.png b/notification/Bubbles/app/src/main/res/mipmap-hdpi/ic_launcher.png
new file mode 100644
index 0000000..add4920
--- /dev/null
+++ b/notification/Bubbles/app/src/main/res/mipmap-hdpi/ic_launcher.png
Binary files differ
diff --git a/notification/Bubbles/app/src/main/res/mipmap-hdpi/ic_launcher_round.png b/notification/Bubbles/app/src/main/res/mipmap-hdpi/ic_launcher_round.png
new file mode 100644
index 0000000..36a7f52
--- /dev/null
+++ b/notification/Bubbles/app/src/main/res/mipmap-hdpi/ic_launcher_round.png
Binary files differ
diff --git a/notification/Bubbles/app/src/main/res/mipmap-mdpi/ic_launcher.png b/notification/Bubbles/app/src/main/res/mipmap-mdpi/ic_launcher.png
new file mode 100644
index 0000000..c1b13dd
--- /dev/null
+++ b/notification/Bubbles/app/src/main/res/mipmap-mdpi/ic_launcher.png
Binary files differ
diff --git a/notification/Bubbles/app/src/main/res/mipmap-mdpi/ic_launcher_round.png b/notification/Bubbles/app/src/main/res/mipmap-mdpi/ic_launcher_round.png
new file mode 100644
index 0000000..d7c39d6
--- /dev/null
+++ b/notification/Bubbles/app/src/main/res/mipmap-mdpi/ic_launcher_round.png
Binary files differ
diff --git a/notification/Bubbles/app/src/main/res/mipmap-xhdpi/ic_launcher.png b/notification/Bubbles/app/src/main/res/mipmap-xhdpi/ic_launcher.png
new file mode 100644
index 0000000..f774729
--- /dev/null
+++ b/notification/Bubbles/app/src/main/res/mipmap-xhdpi/ic_launcher.png
Binary files differ
diff --git a/notification/Bubbles/app/src/main/res/mipmap-xhdpi/ic_launcher_round.png b/notification/Bubbles/app/src/main/res/mipmap-xhdpi/ic_launcher_round.png
new file mode 100644
index 0000000..f0e3e03
--- /dev/null
+++ b/notification/Bubbles/app/src/main/res/mipmap-xhdpi/ic_launcher_round.png
Binary files differ
diff --git a/notification/Bubbles/app/src/main/res/mipmap-xxhdpi/ic_launcher.png b/notification/Bubbles/app/src/main/res/mipmap-xxhdpi/ic_launcher.png
new file mode 100644
index 0000000..85e929f
--- /dev/null
+++ b/notification/Bubbles/app/src/main/res/mipmap-xxhdpi/ic_launcher.png
Binary files differ
diff --git a/notification/Bubbles/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png b/notification/Bubbles/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png
new file mode 100644
index 0000000..156450a
--- /dev/null
+++ b/notification/Bubbles/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png
Binary files differ
diff --git a/notification/Bubbles/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png b/notification/Bubbles/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png
new file mode 100644
index 0000000..51b123c
--- /dev/null
+++ b/notification/Bubbles/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png
Binary files differ
diff --git a/notification/Bubbles/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png b/notification/Bubbles/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png
new file mode 100644
index 0000000..569927d
--- /dev/null
+++ b/notification/Bubbles/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png
Binary files differ
diff --git a/notification/Bubbles/app/src/main/res/transition/app_bar.xml b/notification/Bubbles/app/src/main/res/transition/app_bar.xml
new file mode 100644
index 0000000..462cfd4
--- /dev/null
+++ b/notification/Bubbles/app/src/main/res/transition/app_bar.xml
@@ -0,0 +1,26 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  Copyright (C) 2019 The Android Open Source Project
+
+  Licensed under the Apache License, Version 2.0 (the "License");
+  you may not use this file except in compliance with the License.
+  You may obtain a copy of the License at
+
+       http://www.apache.org/licenses/LICENSE-2.0
+
+  Unless required by applicable law or agreed to in writing, software
+  distributed under the License is distributed on an "AS IS" BASIS,
+  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  See the License for the specific language governing permissions and
+  limitations under the License.
+-->
+<transitionSet
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    android:duration="250"
+    android:interpolator="@android:interpolator/accelerate_decelerate"
+    android:transitionOrdering="together">
+
+    <fade />
+    <changeBounds />
+
+</transitionSet>
diff --git a/notification/Bubbles/app/src/main/res/transition/slide_bottom.xml b/notification/Bubbles/app/src/main/res/transition/slide_bottom.xml
new file mode 100644
index 0000000..f41bfb4
--- /dev/null
+++ b/notification/Bubbles/app/src/main/res/transition/slide_bottom.xml
@@ -0,0 +1,21 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  Copyright (C) 2019 The Android Open Source Project
+
+  Licensed under the Apache License, Version 2.0 (the "License");
+  you may not use this file except in compliance with the License.
+  You may obtain a copy of the License at
+
+       http://www.apache.org/licenses/LICENSE-2.0
+
+  Unless required by applicable law or agreed to in writing, software
+  distributed under the License is distributed on an "AS IS" BASIS,
+  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  See the License for the specific language governing permissions and
+  limitations under the License.
+-->
+<slide
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    android:duration="250"
+    android:interpolator="@android:interpolator/accelerate_decelerate"
+    android:slideEdge="bottom" />
diff --git a/notification/Bubbles/app/src/main/res/transition/slide_top.xml b/notification/Bubbles/app/src/main/res/transition/slide_top.xml
new file mode 100644
index 0000000..9b68cd7
--- /dev/null
+++ b/notification/Bubbles/app/src/main/res/transition/slide_top.xml
@@ -0,0 +1,21 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  Copyright (C) 2019 The Android Open Source Project
+
+  Licensed under the Apache License, Version 2.0 (the "License");
+  you may not use this file except in compliance with the License.
+  You may obtain a copy of the License at
+
+       http://www.apache.org/licenses/LICENSE-2.0
+
+  Unless required by applicable law or agreed to in writing, software
+  distributed under the License is distributed on an "AS IS" BASIS,
+  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  See the License for the specific language governing permissions and
+  limitations under the License.
+-->
+<slide
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    android:duration="250"
+    android:interpolator="@android:interpolator/accelerate_decelerate"
+    android:slideEdge="top" />
diff --git a/notification/Bubbles/app/src/main/res/values/colors.xml b/notification/Bubbles/app/src/main/res/values/colors.xml
new file mode 100644
index 0000000..b4ca87b
--- /dev/null
+++ b/notification/Bubbles/app/src/main/res/values/colors.xml
@@ -0,0 +1,24 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  Copyright (C) 2019 The Android Open Source Project
+
+  Licensed under the Apache License, Version 2.0 (the "License");
+  you may not use this file except in compliance with the License.
+  You may obtain a copy of the License at
+
+       http://www.apache.org/licenses/LICENSE-2.0
+
+  Unless required by applicable law or agreed to in writing, software
+  distributed under the License is distributed on an "AS IS" BASIS,
+  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  See the License for the specific language governing permissions and
+  limitations under the License.
+-->
+<resources>
+    <color name="primary">#008577</color>
+    <color name="primary_dark">#00574B</color>
+    <color name="accent">#D81B60</color>
+
+    <color name="incoming">#FBE9E7</color>
+    <color name="outgoing">#EEEEEE</color>
+</resources>
diff --git a/notification/Bubbles/app/src/main/res/values/dimens.xml b/notification/Bubbles/app/src/main/res/values/dimens.xml
new file mode 100644
index 0000000..be25158
--- /dev/null
+++ b/notification/Bubbles/app/src/main/res/values/dimens.xml
@@ -0,0 +1,26 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  Copyright (C) 2019 The Android Open Source Project
+
+  Licensed under the Apache License, Version 2.0 (the "License");
+  you may not use this file except in compliance with the License.
+  You may obtain a copy of the License at
+
+       http://www.apache.org/licenses/LICENSE-2.0
+
+  Unless required by applicable law or agreed to in writing, software
+  distributed under the License is distributed on an "AS IS" BASIS,
+  WITHOUT 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="spacing_medium">16dp</dimen>
+    <dimen name="spacing_small">8dp</dimen>
+    <dimen name="icon_size">64dp</dimen>
+    <dimen name="app_bar_elevation">4dp</dimen>
+    <dimen name="message_padding_vertical">16dp</dimen>
+    <dimen name="message_padding_horizontal_short">16dp</dimen>
+    <dimen name="message_padding_horizontal_long">24dp</dimen>
+    <dimen name="bubble_height">400dp</dimen>
+</resources>
diff --git a/notification/Bubbles/app/src/main/res/values/ic_launcher_background.xml b/notification/Bubbles/app/src/main/res/values/ic_launcher_background.xml
new file mode 100644
index 0000000..c133350
--- /dev/null
+++ b/notification/Bubbles/app/src/main/res/values/ic_launcher_background.xml
@@ -0,0 +1,4 @@
+<?xml version="1.0" encoding="utf-8"?>
+<resources>
+    <color name="ic_launcher_background">#1C7A71</color>
+</resources>
\ No newline at end of file
diff --git a/notification/Bubbles/app/src/main/res/values/ids.xml b/notification/Bubbles/app/src/main/res/values/ids.xml
new file mode 100644
index 0000000..e205a5b
--- /dev/null
+++ b/notification/Bubbles/app/src/main/res/values/ids.xml
@@ -0,0 +1,19 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  Copyright (C) 2019 The Android Open Source Project
+
+  Licensed under the Apache License, Version 2.0 (the "License");
+  you may not use this file except in compliance with the License.
+  You may obtain a copy of the License at
+
+       http://www.apache.org/licenses/LICENSE-2.0
+
+  Unless required by applicable law or agreed to in writing, software
+  distributed under the License is distributed on an "AS IS" BASIS,
+  WITHOUT 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>
+    <item name="tag_photo" type="id" />
+</resources>
diff --git a/notification/Bubbles/app/src/main/res/values/strings.xml b/notification/Bubbles/app/src/main/res/values/strings.xml
new file mode 100644
index 0000000..aae789e
--- /dev/null
+++ b/notification/Bubbles/app/src/main/res/values/strings.xml
@@ -0,0 +1,29 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  Copyright (C) 2019 The Android Open Source Project
+
+  Licensed under the Apache License, Version 2.0 (the "License");
+  you may not use this file except in compliance with the License.
+  You may obtain a copy of the License at
+
+       http://www.apache.org/licenses/LICENSE-2.0
+
+  Unless required by applicable law or agreed to in writing, software
+  distributed under the License is distributed on an "AS IS" BASIS,
+  WITHOUT 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">Bubbles</string>
+    <string name="show_as_bubble">Show as Bubble</string>
+    <string name="description_icon">Profile icon</string>
+    <string name="description_voice_call">Make a voice call (dummy)</string>
+    <string name="description_send">Send</string>
+    <string name="description_photo">Photo</string>
+    <string name="hint_input">Type a message…</string>
+    <string name="channel_new_messages">New messages</string>
+    <string name="channel_new_messages_description">All new incoming messages.</string>
+    <string name="chat_with_contact">Chat with %s</string>
+    <string name="voice_call_explanation">This is a dummy voice call screen.</string>
+</resources>
diff --git a/notification/Bubbles/app/src/main/res/values/styles.xml b/notification/Bubbles/app/src/main/res/values/styles.xml
new file mode 100644
index 0000000..b85424d
--- /dev/null
+++ b/notification/Bubbles/app/src/main/res/values/styles.xml
@@ -0,0 +1,31 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  Copyright (C) 2019 The Android Open Source Project
+
+  Licensed under the Apache License, Version 2.0 (the "License");
+  you may not use this file except in compliance with the License.
+  You may obtain a copy of the License at
+
+       http://www.apache.org/licenses/LICENSE-2.0
+
+  Unless required by applicable law or agreed to in writing, software
+  distributed under the License is distributed on an "AS IS" BASIS,
+  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  See the License for the specific language governing permissions and
+  limitations under the License.
+-->
+<resources>
+
+    <style name="Theme.Bubbles" parent="Theme.AppCompat.Light.NoActionBar">
+        <item name="colorPrimary">@color/primary</item>
+        <item name="colorPrimaryDark">@color/primary_dark</item>
+        <item name="colorAccent">@color/accent</item>
+    </style>
+
+    <style name="Theme.Bubbles.Voice" parent="Theme.AppCompat.NoActionBar">
+        <item name="colorPrimary">@color/primary</item>
+        <item name="colorPrimaryDark">@color/primary_dark</item>
+        <item name="colorAccent">@color/accent</item>
+    </style>
+
+</resources>
diff --git a/notification/Bubbles/app/src/test/java/com/example/android/bubbles/LiveDataTestUtils.kt b/notification/Bubbles/app/src/test/java/com/example/android/bubbles/LiveDataTestUtils.kt
new file mode 100644
index 0000000..6c74bb3
--- /dev/null
+++ b/notification/Bubbles/app/src/test/java/com/example/android/bubbles/LiveDataTestUtils.kt
@@ -0,0 +1,44 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *       http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR 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.bubbles
+
+import androidx.lifecycle.LiveData
+import androidx.lifecycle.Observer
+import androidx.test.platform.app.InstrumentationRegistry
+import java.util.concurrent.CountDownLatch
+import java.util.concurrent.TimeUnit
+
+/**
+ * Observes this [LiveData] and returns the value.
+ *
+ * @throws NullPointerException if the observed value is null.
+ */
+fun <T> LiveData<T>.observedValue(): T {
+    var result: T? = null
+    val latch = CountDownLatch(1)
+    val observer = Observer<T> {
+        result = it
+        latch.countDown()
+    }
+    InstrumentationRegistry.getInstrumentation().runOnMainSync {
+        observeForever(observer)
+    }
+    latch.await(3000L, TimeUnit.MILLISECONDS)
+    InstrumentationRegistry.getInstrumentation().runOnMainSync {
+        removeObserver(observer)
+    }
+    return result!!
+}
diff --git a/notification/Bubbles/app/src/test/java/com/example/android/bubbles/data/TestChatRepository.kt b/notification/Bubbles/app/src/test/java/com/example/android/bubbles/data/TestChatRepository.kt
new file mode 100644
index 0000000..65d8d9c
--- /dev/null
+++ b/notification/Bubbles/app/src/test/java/com/example/android/bubbles/data/TestChatRepository.kt
@@ -0,0 +1,88 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *       http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR 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.bubbles.data
+
+import androidx.lifecycle.LiveData
+import androidx.lifecycle.MutableLiveData
+
+/**
+ * This is like [DefaultChatRepository] except:
+ * - The initial chat history can be supplied as a constructor parameter.
+ * - It does not wait 5 seconds to receive a reply.
+ */
+class TestChatRepository(private val chats: Map<Long, Chat>) : ChatRepository {
+
+    var activatedId = 0L
+
+    var bubbleId = 0L
+
+    override fun getContacts(): LiveData<List<Contact>> {
+        return MutableLiveData<List<Contact>>().apply {
+            value = chats.values.map { it.contact }
+        }
+    }
+
+    override fun findContact(id: Long): LiveData<Contact?> {
+        return MutableLiveData<Contact>().apply {
+            value = Contact.CONTACTS.find { it.id == id }
+        }
+    }
+
+    override fun findMessages(id: Long): LiveData<List<Message>> {
+        val chat = chats.getValue(id)
+        return object : LiveData<List<Message>>() {
+
+            private val listener = { messages: List<Message> ->
+                postValue(messages)
+            }
+
+            override fun onActive() {
+                value = chat.messages
+                chat.addListener(listener)
+            }
+
+            override fun onInactive() {
+                chat.removeListener(listener)
+            }
+        }
+    }
+
+    override fun sendMessage(id: Long, text: String) {
+        val chat = chats.getValue(id)
+        chat.addMessage(Message.Builder().apply {
+            sender = 0L // User
+            this.text = text
+            timestamp = System.currentTimeMillis()
+        })
+        chat.addMessage(chat.contact.reply(text))
+    }
+
+    override fun activateChat(id: Long) {
+        activatedId = id
+    }
+
+    override fun deactivateChat(id: Long) {
+        activatedId = 0L
+    }
+
+    override fun showAsBubble(id: Long) {
+        bubbleId = id
+    }
+
+    override fun canBubble(): Boolean {
+        return true
+    }
+}
diff --git a/notification/Bubbles/app/src/test/java/com/example/android/bubbles/ui/chat/ChatViewModelTest.kt b/notification/Bubbles/app/src/test/java/com/example/android/bubbles/ui/chat/ChatViewModelTest.kt
new file mode 100644
index 0000000..6a13639
--- /dev/null
+++ b/notification/Bubbles/app/src/test/java/com/example/android/bubbles/ui/chat/ChatViewModelTest.kt
@@ -0,0 +1,82 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *       http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR 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.bubbles.ui.chat
+
+import androidx.arch.core.executor.testing.InstantTaskExecutorRule
+import androidx.test.core.app.ApplicationProvider
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import androidx.test.platform.app.InstrumentationRegistry
+import com.example.android.bubbles.data.Chat
+import com.example.android.bubbles.data.Contact
+import com.example.android.bubbles.data.TestChatRepository
+import com.example.android.bubbles.observedValue
+import com.google.common.truth.Truth.assertThat
+import org.junit.Before
+import org.junit.Rule
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@RunWith(AndroidJUnit4::class)
+@SmallTest
+class ChatViewModelTest {
+
+    @get:Rule
+    val instantTaskExecutorRule = InstantTaskExecutorRule()
+
+    private val dummyContacts = Contact.CONTACTS
+
+    private lateinit var viewModel: ChatViewModel
+    private lateinit var repository: TestChatRepository
+
+    @Before
+    fun createViewModel() {
+        repository = TestChatRepository(dummyContacts.map { contact ->
+            contact.id to Chat(contact)
+        }.toMap())
+        InstrumentationRegistry.getInstrumentation().runOnMainSync {
+            viewModel = ChatViewModel(ApplicationProvider.getApplicationContext(), repository)
+        }
+    }
+
+    @Test
+    fun hasContactAndMessages() {
+        viewModel.setChatId(1L)
+        viewModel.foreground = true
+        assertThat(viewModel.contact.observedValue()).isEqualTo(dummyContacts.find { it.id == 1L })
+        assertThat(viewModel.messages.observedValue()).hasSize(2)
+        assertThat(repository.activatedId).isEqualTo(1L)
+    }
+
+    @Test
+    fun sendAndReceiveReply() {
+        viewModel.setChatId(1L)
+        viewModel.send("a")
+        val messages = viewModel.messages.observedValue()
+        assertThat(messages).hasSize(4)
+        assertThat(messages[2].text).isEqualTo("a")
+        assertThat(messages[3].text).isEqualTo("Meow")
+    }
+
+    @Test
+    fun showAsBubble() {
+        viewModel.setChatId(1L)
+        assertThat(repository.bubbleId).isEqualTo(0L)
+        viewModel.showAsBubble()
+        assertThat(repository.bubbleId).isEqualTo(1L)
+    }
+
+}
diff --git a/notification/Bubbles/app/src/test/java/com/example/android/bubbles/ui/main/MainViewModelTest.kt b/notification/Bubbles/app/src/test/java/com/example/android/bubbles/ui/main/MainViewModelTest.kt
new file mode 100644
index 0000000..4f1f5e6
--- /dev/null
+++ b/notification/Bubbles/app/src/test/java/com/example/android/bubbles/ui/main/MainViewModelTest.kt
@@ -0,0 +1,61 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *       http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR 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.bubbles.ui.main
+
+import androidx.arch.core.executor.testing.InstantTaskExecutorRule
+import androidx.test.core.app.ApplicationProvider
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import androidx.test.platform.app.InstrumentationRegistry
+import com.example.android.bubbles.data.Chat
+import com.example.android.bubbles.data.Contact
+import com.example.android.bubbles.data.TestChatRepository
+import com.google.common.truth.Truth.assertThat
+import org.junit.Before
+import org.junit.Rule
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@RunWith(AndroidJUnit4::class)
+@SmallTest
+class MainViewModelTest {
+
+    @get:Rule
+    val instantTaskExecutorRule = InstantTaskExecutorRule()
+
+    private val dummyContacts = Contact.CONTACTS
+
+    private fun createViewModel(): MainViewModel {
+        var viewModel: MainViewModel? = null
+        InstrumentationRegistry.getInstrumentation().runOnMainSync {
+            viewModel = MainViewModel(
+                ApplicationProvider.getApplicationContext(),
+                TestChatRepository(dummyContacts.map { contact ->
+                    contact.id to Chat(contact)
+                }.toMap())
+            )
+        }
+        return viewModel!!
+    }
+
+    @Test
+    fun hasListOfContacts() {
+        val viewModel = createViewModel()
+        val contacts = viewModel.contacts.value
+        assertThat(contacts).isEqualTo(dummyContacts)
+    }
+
+}
diff --git a/notification/Bubbles/build.gradle b/notification/Bubbles/build.gradle
new file mode 100644
index 0000000..d1db5ac
--- /dev/null
+++ b/notification/Bubbles/build.gradle
@@ -0,0 +1,36 @@
+// Top-level build file where you can add configuration options common to all sub-projects/modules.
+
+buildscript {
+    ext.kotlin_version = '1.3.21'
+    repositories {
+        google()
+        jcenter()
+    }
+    dependencies {
+        classpath 'com.android.tools.build:gradle:3.3.2'
+        classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
+    }
+}
+
+allprojects {
+    repositories {
+        google()
+        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/notification/Bubbles/buildSrc/build.gradle b/notification/Bubbles/buildSrc/build.gradle
new file mode 100644
index 0000000..8963e1f
--- /dev/null
+++ b/notification/Bubbles/buildSrc/build.gradle
@@ -0,0 +1,18 @@
+
+repositories {
+    google()
+    jcenter()
+    mavenCentral()
+}
+dependencies {
+    compile 'org.freemarker:freemarker:2.3.20'
+}
+
+sourceSets {
+    main {
+        groovy {
+            srcDir new File(rootDir, "../../../../../build/buildSrc/src/main/groovy")
+        }
+    }
+}
+
diff --git a/notification/Bubbles/gradle.properties b/notification/Bubbles/gradle.properties
new file mode 100644
index 0000000..23339e0
--- /dev/null
+++ b/notification/Bubbles/gradle.properties
@@ -0,0 +1,21 @@
+# 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
+# AndroidX package structure to make it clearer which packages are bundled with the
+# Android operating system, and which are packaged with your app's APK
+# https://developer.android.com/topic/libraries/support-library/androidx-rn
+android.useAndroidX=true
+# Automatically convert third-party libraries to use AndroidX
+android.enableJetifier=true
+# Kotlin code style for this project: "official" or "obsolete":
+kotlin.code.style=official
diff --git a/notification/Bubbles/gradle/wrapper/gradle-wrapper.jar b/notification/Bubbles/gradle/wrapper/gradle-wrapper.jar
new file mode 100644
index 0000000..87b738c
--- /dev/null
+++ b/notification/Bubbles/gradle/wrapper/gradle-wrapper.jar
Binary files differ
diff --git a/notification/Bubbles/gradle/wrapper/gradle-wrapper.properties b/notification/Bubbles/gradle/wrapper/gradle-wrapper.properties
new file mode 100644
index 0000000..ae45383
--- /dev/null
+++ b/notification/Bubbles/gradle/wrapper/gradle-wrapper.properties
@@ -0,0 +1,5 @@
+distributionBase=GRADLE_USER_HOME
+distributionPath=wrapper/dists
+distributionUrl=https\://services.gradle.org/distributions/gradle-4.10.3-all.zip
+zipStoreBase=GRADLE_USER_HOME
+zipStorePath=wrapper/dists
diff --git a/notification/Bubbles/gradlew b/notification/Bubbles/gradlew
new file mode 100755
index 0000000..af6708f
--- /dev/null
+++ b/notification/Bubbles/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='"-Xmx64m"'
+
+# 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 run from Finder on Mac, the cwd is wrong
+if [ "$(uname)" = "Darwin" ] && [ "$HOME" = "$PWD" ]; then
+  cd "$(dirname "$0")"
+fi
+
+exec "$JAVACMD" "$@"
diff --git a/notification/Bubbles/gradlew.bat b/notification/Bubbles/gradlew.bat
new file mode 100644
index 0000000..0f8d593
--- /dev/null
+++ b/notification/Bubbles/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="-Xmx64m"

+

+@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/notification/Bubbles/screenshots/bubble.png b/notification/Bubbles/screenshots/bubble.png
new file mode 100644
index 0000000..bfa55cf
--- /dev/null
+++ b/notification/Bubbles/screenshots/bubble.png
Binary files differ
diff --git a/notification/Bubbles/screenshots/chat.png b/notification/Bubbles/screenshots/chat.png
new file mode 100644
index 0000000..55c475b
--- /dev/null
+++ b/notification/Bubbles/screenshots/chat.png
Binary files differ
diff --git a/notification/Bubbles/screenshots/icon-web.png b/notification/Bubbles/screenshots/icon-web.png
new file mode 100644
index 0000000..2c18de9
--- /dev/null
+++ b/notification/Bubbles/screenshots/icon-web.png
Binary files differ
diff --git a/notification/Bubbles/screenshots/main.png b/notification/Bubbles/screenshots/main.png
new file mode 100644
index 0000000..ab89c97
--- /dev/null
+++ b/notification/Bubbles/screenshots/main.png
Binary files differ
diff --git a/notification/Bubbles/settings.gradle b/notification/Bubbles/settings.gradle
new file mode 100644
index 0000000..e7b4def
--- /dev/null
+++ b/notification/Bubbles/settings.gradle
@@ -0,0 +1 @@
+include ':app'
diff --git a/notification/Bubbles/template-params.xml b/notification/Bubbles/template-params.xml
new file mode 100644
index 0000000..7c59c34
--- /dev/null
+++ b/notification/Bubbles/template-params.xml
@@ -0,0 +1,114 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  Copyright (C) 2019 The Android Open Source Project
+
+  Licensed under the Apache License, Version 2.0 (the "License");
+  you may not use this file except in compliance with the License.
+  You may obtain a copy of the License at
+
+       http://www.apache.org/licenses/LICENSE-2.0
+
+  Unless required by applicable law or agreed to in writing, software
+  distributed under the License is distributed on an "AS IS" BASIS,
+  WITHOUT 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>Bubbles</name>
+    <group>Notification</group>
+    <package>com.example.android.bubbles</package>
+
+    <minSdk>Q</minSdk>
+
+    <strings>
+        <intro>
+            <![CDATA[
+This sample demonstrates how to use Bubbles API to show notifications as bubbles.
+]]>
+        </intro>
+    </strings>
+
+    <tempate src="base-build" />
+
+    <metadata>
+        <status>PUBLISHED</status>
+        <categories>Notification</categories>
+        <technologies>Android</technologies>
+        <languages>Kotlin</languages>
+        <solutions>Mobile</solutions>
+        <level>INTERMEDIATE</level>
+        <icon>screenshots/icon-web.png</icon>
+        <screenshots>
+            <img>screenshots/main.png</img>
+            <img>screenshots/chat.png</img>
+        </screenshots>
+        <api_refs>
+            <android>android.app.Notification.BubbleMetadata</android>
+        </api_refs>
+        <description>
+            This sample demonstrates how to use Bubbles API to show notifications as bubbles.
+        </description>
+        <intro>
+<![CDATA[
+### API Usage
+
+In order to show a notification as a bubble, call [setBubbleMetadata][1] and set metadata to the
+Notification.Builder. [Notification.BubbleMetadata.Builder] can be used to create the metadata. You
+can set the icon and the desired height of the bubble. The notification has to be in a notification
+channel with [IMPORTANCE_HIGH][2] in order to prompt the user to allow bubbles.
+
+When the bubble is clicked, it expands to a small window. This is called an expanded bubble. An
+expanded bubble is an Activity. Use [setIntent][3] to specify an Activity to be lauched as the
+expanded bubble.  The Activity must be [resizeable][4] and [embedded][5]. It also has to be able to
+launch as multiple instances, so set [documentLaunchMode][6] to "always".
+
+You might want to provide a feature to let users explicitly show an expanded bubble when they
+perform some actions, e.g. tapping on a button to show content in a bubble. For this, call
+[setAutoExpandBubble][7] on BubbleMetadata.Builder. It may also make sense to suppress the initial
+notification using [setSuppressInitialNotification][8] in this situation. These flags work only when
+your app is in the foreground. *This feature does not yet work in Android Q Beta 2.*
+
+Bubbles can also be shown explicitly when the app is in the foreground.
+
+[1]: https://developer.android.com/reference/android/app/Notification.Builder.html#setBubbleMetadata
+[2]: https://developer.android.com/reference/android/app/NotificationManager.html#IMPORTANCE_HIGH
+[3]: https://developer.android.com/reference/android/app/Notification.BubbleMetadata.Builder.html#setIntent
+[4]: https://developer.android.com/guide/topics/manifest/activity-element.html#resizeableActivity
+[5]: https://developer.android.com/guide/topics/manifest/activity-element.html#embedded
+[6]: https://developer.android.com/guide/topics/manifest/activity-element.html#dlmode
+[7]: https://developer.android.com/reference/android/app/Notification.BubbleMetadata.Builder.html#setAutoExpandBubble
+[8]: https://developer.android.com/reference/android/app/Notification.BubbleMetadata.Builder.html#setSuppressInitialNotification
+
+### When to use Bubbles instead of normal notifications
+
+Bubbles take up screen real estate and cover other app content. You should only send a notification
+as a bubble if it is important enough such as ongoing communications, or if the user has explicitly
+requested a bubble for some content.
+
+### When Bubble is disabled
+
+Note that the bubble can be disabled by the user. In that case, a bubble notification is shown as a
+normal notification. You should always make sure your bubble notification works as a normal
+notification as well.
+
+### Expanded Bubbles
+
+System UI allows the user to horizontally swipe through expanded bubbles. You should avoid
+horizontal scrolling content in your Activity for expanded bubbles.
+
+### About the sample
+
+The sample is a dummy chat app. Talk to one of the chat bots, and it will reply back to you in
+several seconds. The notification is not shown when the app is in the foreground, so just press back
+or home before you get a reply.
+
+- cat.jpg: Photo by [Erik-Jan Leusink](https://unsplash.com/photos/IbPxGLgJiMI?utm_source=unsplash&utm_medium=referral&utm_content=creditCopyText) on [Unsplash](https://unsplash.com/search/photos/cat?utm_source=unsplash&utm_medium=referral&utm_content=creditCopyText)
+- dog.jpg: Photo by [Lui Peng](https://unsplash.com/photos/ybHtKz5He9Y?utm_source=unsplash&utm_medium=referral&utm_content=creditCopyText) on Unsplash
+- parrot.jpg: Photo by [Nikolay Tchaouchev](https://unsplash.com/photos/jJuq6oNfgRo?utm_source=unsplash&utm_medium=referral&utm_content=creditCopyText) on Unsplash
+- sheep.jpg, sheep-full.jpg: Photo by [Luke Stackpoole](https://unsplash.com/photos/sfB_Nw9sggw?utm_source=unsplash&utm_medium=referral&utm_content=creditCopyText) on Unsplash
+]]>
+        </intro>
+    </metadata>
+
+</sample>
diff --git a/projects.txt b/projects.txt
index 84d6fc3..6fa29dc 100644
--- a/projects.txt
+++ b/projects.txt
@@ -115,3 +115,4 @@
 views/EmojiCompat
 ui/fonts/DownloadableFonts
 wearable/wear/WearComplicationProvidersTestSuite
+content/SharingShortcuts
diff --git a/security/FingerprintDialog/Application/src/main/Android.mk b/security/FingerprintDialog/Application/src/main/Android.mk
index 30420c0..54ec850 100644
--- a/security/FingerprintDialog/Application/src/main/Android.mk
+++ b/security/FingerprintDialog/Application/src/main/Android.mk
@@ -14,16 +14,17 @@
 
 LOCAL_PATH:= $(call my-dir)
 include $(CLEAR_VARS)
-LOCAL_STATIC_JAVA_LIBRARIES := \
-    android-support-v7-appcompat \
-    android-support-v4 \
-    android-support-annotations
+LOCAL_USE_AAPT2 := true
+LOCAL_STATIC_ANDROID_LIBRARIES := \
+    androidx.appcompat_appcompat \
+    androidx.legacy_legacy-support-v4 \
+    androidx.annotation_annotation
 LOCAL_MODULE_TAGS := samples
 LOCAL_SRC_FILES := $(call all-subdir-java-files)
 LOCAL_PACKAGE_NAME := FingerprintDialog
 LOCAL_SDK_VERSION := current
 LOCAL_RESOURCE_DIR := $(LOCAL_PATH)/res \
-    frameworks/support/v7/appcompat/res
+    prebuilts/sdk/current/support/v7/appcompat/res
 LOCAL_AAPT_FLAGS := --auto-add-overlay \
     --extra-packages android.support.v7.appcompat
 
diff --git a/security/FingerprintDialog/Application/src/main/java/com/example/android/fingerprintdialog/MainActivity.java b/security/FingerprintDialog/Application/src/main/java/com/example/android/fingerprintdialog/MainActivity.java
index e30b4c2..867f6f9 100644
--- a/security/FingerprintDialog/Application/src/main/java/com/example/android/fingerprintdialog/MainActivity.java
+++ b/security/FingerprintDialog/Application/src/main/java/com/example/android/fingerprintdialog/MainActivity.java
@@ -26,9 +26,9 @@
 import android.security.keystore.KeyGenParameterSpec;
 import android.security.keystore.KeyPermanentlyInvalidatedException;
 import android.security.keystore.KeyProperties;
-import android.support.annotation.Nullable;
-import android.support.v7.app.AppCompatActivity;
-import android.support.v7.widget.Toolbar;
+import androidx.annotation.Nullable;
+import androidx.appcompat.app.AppCompatActivity;
+import androidx.appcompat.widget.Toolbar;
 import android.util.Base64;
 import android.util.Log;
 import android.view.Menu;
diff --git a/security/FingerprintDialog/Application/src/main/java/com/example/android/fingerprintdialog/SettingsActivity.java b/security/FingerprintDialog/Application/src/main/java/com/example/android/fingerprintdialog/SettingsActivity.java
index 4ac8846..5ae7518 100644
--- a/security/FingerprintDialog/Application/src/main/java/com/example/android/fingerprintdialog/SettingsActivity.java
+++ b/security/FingerprintDialog/Application/src/main/java/com/example/android/fingerprintdialog/SettingsActivity.java
@@ -18,7 +18,7 @@
 
 import android.os.Bundle;
 import android.preference.PreferenceFragment;
-import android.support.v7.app.AppCompatActivity;
+import androidx.appcompat.app.AppCompatActivity;
 
 public class SettingsActivity extends AppCompatActivity {
 
diff --git a/security/FingerprintDialog/Application/src/main/res/layout/activity_main.xml b/security/FingerprintDialog/Application/src/main/res/layout/activity_main.xml
index 130bc8c..956f576 100644
--- a/security/FingerprintDialog/Application/src/main/res/layout/activity_main.xml
+++ b/security/FingerprintDialog/Application/src/main/res/layout/activity_main.xml
@@ -24,7 +24,7 @@
         android:layout_height="wrap_content"
         android:orientation="vertical">
 
-        <android.support.v7.widget.Toolbar
+        <androidx.appcompat.widget.Toolbar
             android:id="@+id/toolbar"
             android:layout_width="match_parent"
             android:layout_height="?attr/actionBarSize"
diff --git a/ui/text/README.md b/ui/text/README.md
index e611954..8d08bc8 100644
--- a/ui/text/README.md
+++ b/ui/text/README.md
@@ -1,47 +1,19 @@
-Text Styling
-============
-These samples shows how to style text on Android using spans, in [Java](https://github.com/googlesamples/android-text/tree/master/TextStyling-Java) and in [Kotlin](https://github.com/googlesamples/android-text/tree/master/TextStyling-Kotlin).
+Android Text Samples
+====================
+These samples show how to work with text in Android.
 
+Explore the samples
+-------------------
+The **TextStyling** [Java](https://github.com/googlesamples/android-text/tree/master/TextStyling-Java) and [Kotlin](https://github.com/googlesamples/android-text/tree/master/TextStyling-Kotlin) samples show how to style text using spans.
 
-Introduction
-------------
-The difference between [TextStyling-Java](https://github.com/googlesamples/android-text/tree/master/TextStyling-Java) and [TextStyling-Kotlin](https://github.com/googlesamples/android-text/tree/master/TextStyling-Kotlin) is only in the language. They have the same set of features, same class names and similar ways of testing the functionality.
-## Features
-Parse some hardcoded text and do the following:
-* Paragraphs starting with “> ” are transformed into quotes.
-* Text enclosed in “```” will be transformed into inline code block.
-* Lines starting with “+ ” or “* ” will be transformed into bullet points.
-To update the text, modify the value of `R.string.display_text`.
-This project is not meant to fully cover the markdown capabilities and has several limitations; for example, quotes do not support nesting other elements.
-
-## Implementation
-The text is parsed in the `Parser.parse` method and the spans are created in the `MarkdownBuilder.markdownToSpans` method.
-To see how to apply a span, check out `MarkdownBuilder.buildCodeBlockSpan`. To see how to apply multiple spans on the same string, see `MarkdownBuilder.buildQuoteSpan`. For examples of creating custom spans, see `BulletPointSpan`, `CodeBlockSpan` or `FontSpan`.
-
-## Testing
-Text parsing is tested with JUnit tests in `ParserTest`. Span building is tested via Android JUnit tests, in `MarkdownBuilderTest`.
-
-
-Getting Started
----------------
-
-Clone this repository, enter the top level directory and run `./gradlew tasks`
-to get an overview of all the tasks available for this project.
-
-Some important tasks are:
-
-```
-assembleDebug - Assembles all Debug builds.
-installDebug - Installs the Debug build.
-connectedAndroidTest - Installs and runs the tests for Debug build on connected
-devices.
-test - Run all unit tests.
-```
+The **RoundedBackground** sample shows how to draw a rounded corner background on a text that extends across one or multiple lines, supporting right-to-left text also.
 
 Screenshots
 -----------
 <img src="screenshots/main_activity.png" width="30%" />
 
+<img src="screenshots/rounded_bg.png" width="30%" />
+
 Support
 -------
 - Stack Overflow: http://stackoverflow.com/questions/tagged/android-text
@@ -72,4 +44,3 @@
 License for the specific language governing permissions and limitations under
 the License.
 ```
-
diff --git a/ui/text/RoundedBackground-Kotlin/README.md b/ui/text/RoundedBackground-Kotlin/README.md
new file mode 100644
index 0000000..8bd96e7
--- /dev/null
+++ b/ui/text/RoundedBackground-Kotlin/README.md
@@ -0,0 +1,93 @@
+Drawing a rounded corner background on text
+============
+
+This sample shows how to draw a **rounded** corner background on text. It supports the following cases:
+
+* Set the background on **one line** text
+
+<img src="../screenshots/single.png" width="30%" />
+
+* Set the background on text over **two or multiple lines**
+
+<img src="../screenshots/multi.png" width="30%" />
+
+* Set the background on **right-to-left** text
+
+<img src="../screenshots/rtl.png" width="30%" />
+
+
+Implementation
+---------------
+Depending on the position of the text, we need to draw four different drawables as text backgrounds:
+
+* Text fits on one line: we only need one drawable
+* Text fits on 2 lines: we need drawables for the start and end of the text
+* Text spans multiple lines: we need drawables for the start, middle and end of the text
+
+<img src="../screenshots/lines.png" width="30%" />
+
+The four drawables that need to be drawn depending on the position of the text:
+
+To position the background, we need to:
+* Determine whether the text spans multiple lines
+* Find the start and end lines
+* Find the start and end offset depending on the paragraph direction
+
+All of these can be computed based on the text Layout. To render the background behind the text we need access to the Canvas. A custom TextView has access to all of the information necessary to position the drawables and render them.
+
+Our solution involves splitting our problem into 4 parts and creating classes dealing with them individually:
+* **Marking the position of the background** is done in the XML resources via Annotation spans and then, in the code, we read them in the `TextRoundedBgHelper`
+* Providing the background **drawables as attributes** of the TextView - implemented in `TextRoundedBgAttributeReader`
+* **Rendering the drawables** depending on whether the text runs across **one or multiple lines** - `TextRoundedBgHelper` interface and its implementations: `SingleLineRenderer` and `MultiLineRenderer`
+* Supporting **custom drawing** on a TextView - `RoundedBgTextView`, a class that extends `AppCompatTextView`, reads the attributes with the help of `TextRoundedBgAttributeReader`, overrides `onDraw` where it uses `TextRoundedBgHelper` to draw the background.
+
+Getting Started
+---------------
+
+Clone this repository, enter the top level directory and run `./gradlew tasks`
+to get an overview of all the tasks available for this project.
+
+Some important tasks are:
+
+```
+assembleDebug - Assembles all Debug builds.
+installDebug - Installs the Debug build.
+connectedAndroidTest - Installs and runs the tests for Debug build on connected
+devices.
+test - Run all unit tests.
+```
+
+Screenshots
+-----------
+<img src="../screenshots/rounded_bg.png" width="30%" />
+
+Support
+-------
+- Stack Overflow: http://stackoverflow.com/questions/tagged/android-text
+
+If you've found an error in this sample, please file an issue:
+https://github.com/googlesamples/android-text/issues
+
+Patches are encouraged, and may be submitted by forking this project and
+submitting a pull request through GitHub.
+
+License
+--------
+```
+Copyright 2018 The Android Open Source Project
+
+Licensed to the Apache Software Foundation (ASF) under one or more contributor
+license agreements. See the NOTICE file distributed with this work for
+additional information regarding copyright ownership. The ASF licenses this
+file to you under the Apache License, Version 2.0 (the "License"); you may not
+use this file except in compliance with the License. You may obtain a copy of
+the License at
+
+   http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+License for the specific language governing permissions and limitations under
+the License.
+```
diff --git a/ui/text/RoundedBackground-Kotlin/app/build.gradle b/ui/text/RoundedBackground-Kotlin/app/build.gradle
new file mode 100644
index 0000000..0b78ce1
--- /dev/null
+++ b/ui/text/RoundedBackground-Kotlin/app/build.gradle
@@ -0,0 +1,43 @@
+/*
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT 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'
+
+apply plugin: 'kotlin-android'
+
+
+android {
+    compileSdkVersion rootProject.compileSdkVersion
+
+    defaultConfig {
+        applicationId "com.android.example.text.styling.roundedbg"
+        minSdkVersion rootProject.minSdkVersion
+        targetSdkVersion rootProject.targetSdkVersion
+        versionCode 1
+        versionName "1.0"
+    }
+    buildTypes {
+        release {
+            minifyEnabled false
+            proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
+        }
+    }
+}
+
+dependencies {
+    implementation project(':lib')
+    implementation "androidx.appcompat:appcompat:$androidxVersion"
+    implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version"
+}
\ No newline at end of file
diff --git a/ui/text/RoundedBackground-Kotlin/app/src/main/AndroidManifest.xml b/ui/text/RoundedBackground-Kotlin/app/src/main/AndroidManifest.xml
new file mode 100644
index 0000000..d7429d4
--- /dev/null
+++ b/ui/text/RoundedBackground-Kotlin/app/src/main/AndroidManifest.xml
@@ -0,0 +1,35 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  ~ Copyright 2018 The Android Open Source Project
+  ~
+  ~ Licensed under the Apache License, Version 2.0 (the "License");
+  ~ you may not use this file except in compliance with the License.
+  ~ You may obtain a copy of the License at
+  ~
+  ~      http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing, software
+  ~ distributed under the License is distributed on an "AS IS" BASIS,
+  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  ~ See the License for the specific language governing permissions and
+  ~ limitations under the License.
+  -->
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+          package="com.android.example.text.styling.roundedbg.app">
+
+    <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/Theme.RoundedBackground">
+        <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>
\ No newline at end of file
diff --git a/ui/text/RoundedBackground-Kotlin/app/src/main/java/com/android/example/text/styling/roundedbg/app/MainActivity.kt b/ui/text/RoundedBackground-Kotlin/app/src/main/java/com/android/example/text/styling/roundedbg/app/MainActivity.kt
new file mode 100644
index 0000000..53ae397
--- /dev/null
+++ b/ui/text/RoundedBackground-Kotlin/app/src/main/java/com/android/example/text/styling/roundedbg/app/MainActivity.kt
@@ -0,0 +1,30 @@
+/*
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+*/
+package com.android.example.text.styling.roundedbg.app
+
+import android.os.Bundle
+import androidx.appcompat.app.AppCompatActivity
+
+/**
+ * Sample activity that uses [com.android.example.text.styling.roundedbg.RoundedBgTextView].
+ */
+class MainActivity : AppCompatActivity() {
+
+    override fun onCreate(savedInstanceState: Bundle?) {
+        super.onCreate(savedInstanceState)
+        setContentView(R.layout.activity_main)
+    }
+}
diff --git a/ui/text/RoundedBackground-Kotlin/app/src/main/res/drawable-v24/ic_launcher_foreground.xml b/ui/text/RoundedBackground-Kotlin/app/src/main/res/drawable-v24/ic_launcher_foreground.xml
new file mode 100644
index 0000000..888da70
--- /dev/null
+++ b/ui/text/RoundedBackground-Kotlin/app/src/main/res/drawable-v24/ic_launcher_foreground.xml
@@ -0,0 +1,50 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  ~ Copyright 2018 The Android Open Source Project
+  ~
+  ~ Licensed under the Apache License, Version 2.0 (the "License");
+  ~ you may not use this file except in compliance with the License.
+  ~ You may obtain a copy of the License at
+  ~
+  ~      http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing, software
+  ~ distributed under the License is distributed on an "AS IS" BASIS,
+  ~ WITHOUT 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"
+    xmlns:aapt="http://schemas.android.com/aapt"
+    android:width="108dp"
+    android:height="108dp"
+    android:viewportHeight="108"
+    android:viewportWidth="108">
+    <path
+        android:fillType="evenOdd"
+        android:pathData="M32,64C32,64 38.39,52.99 44.13,50.95C51.37,48.37 70.14,49.57 70.14,49.57L108.26,87.69L108,109.01L75.97,107.97L32,64Z"
+        android:strokeColor="#00000000"
+        android:strokeWidth="1">
+        <aapt:attr name="android:fillColor">
+            <gradient
+                android:endX="78.5885"
+                android:endY="90.9159"
+                android:startX="48.7653"
+                android:startY="61.0927"
+                android:type="linear">
+                <item
+                    android:color="#44000000"
+                    android:offset="0.0" />
+                <item
+                    android:color="#00000000"
+                    android:offset="1.0" />
+            </gradient>
+        </aapt:attr>
+    </path>
+    <path
+        android:fillColor="#FFFFFF"
+        android:fillType="nonZero"
+        android:pathData="M66.94,46.02L66.94,46.02C72.44,50.07 76,56.61 76,64L32,64C32,56.61 35.56,50.11 40.98,46.06L36.18,41.19C35.45,40.45 35.45,39.3 36.18,38.56C36.91,37.81 38.05,37.81 38.78,38.56L44.25,44.05C47.18,42.57 50.48,41.71 54,41.71C57.48,41.71 60.78,42.57 63.68,44.05L69.11,38.56C69.84,37.81 70.98,37.81 71.71,38.56C72.44,39.3 72.44,40.45 71.71,41.19L66.94,46.02ZM62.94,56.92C64.08,56.92 65,56.01 65,54.88C65,53.76 64.08,52.85 62.94,52.85C61.8,52.85 60.88,53.76 60.88,54.88C60.88,56.01 61.8,56.92 62.94,56.92ZM45.06,56.92C46.2,56.92 47.13,56.01 47.13,54.88C47.13,53.76 46.2,52.85 45.06,52.85C43.92,52.85 43,53.76 43,54.88C43,56.01 43.92,56.92 45.06,56.92Z"
+        android:strokeColor="#00000000"
+        android:strokeWidth="1" />
+</vector>
diff --git a/ui/text/RoundedBackground-Kotlin/app/src/main/res/drawable/ic_launcher_background.xml b/ui/text/RoundedBackground-Kotlin/app/src/main/res/drawable/ic_launcher_background.xml
new file mode 100644
index 0000000..a5e4913
--- /dev/null
+++ b/ui/text/RoundedBackground-Kotlin/app/src/main/res/drawable/ic_launcher_background.xml
@@ -0,0 +1,185 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  ~ Copyright 2018 The Android Open Source Project
+  ~
+  ~ Licensed under the Apache License, Version 2.0 (the "License");
+  ~ you may not use this file except in compliance with the License.
+  ~ You may obtain a copy of the License at
+  ~
+  ~      http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing, software
+  ~ distributed under the License is distributed on an "AS IS" BASIS,
+  ~ WITHOUT 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="108dp"
+    android:height="108dp"
+    android:viewportHeight="108"
+    android:viewportWidth="108">
+    <path
+        android:fillColor="#26A69A"
+        android:pathData="M0,0h108v108h-108z" />
+    <path
+        android:fillColor="#00000000"
+        android:pathData="M9,0L9,108"
+        android:strokeColor="#33FFFFFF"
+        android:strokeWidth="0.8" />
+    <path
+        android:fillColor="#00000000"
+        android:pathData="M19,0L19,108"
+        android:strokeColor="#33FFFFFF"
+        android:strokeWidth="0.8" />
+    <path
+        android:fillColor="#00000000"
+        android:pathData="M29,0L29,108"
+        android:strokeColor="#33FFFFFF"
+        android:strokeWidth="0.8" />
+    <path
+        android:fillColor="#00000000"
+        android:pathData="M39,0L39,108"
+        android:strokeColor="#33FFFFFF"
+        android:strokeWidth="0.8" />
+    <path
+        android:fillColor="#00000000"
+        android:pathData="M49,0L49,108"
+        android:strokeColor="#33FFFFFF"
+        android:strokeWidth="0.8" />
+    <path
+        android:fillColor="#00000000"
+        android:pathData="M59,0L59,108"
+        android:strokeColor="#33FFFFFF"
+        android:strokeWidth="0.8" />
+    <path
+        android:fillColor="#00000000"
+        android:pathData="M69,0L69,108"
+        android:strokeColor="#33FFFFFF"
+        android:strokeWidth="0.8" />
+    <path
+        android:fillColor="#00000000"
+        android:pathData="M79,0L79,108"
+        android:strokeColor="#33FFFFFF"
+        android:strokeWidth="0.8" />
+    <path
+        android:fillColor="#00000000"
+        android:pathData="M89,0L89,108"
+        android:strokeColor="#33FFFFFF"
+        android:strokeWidth="0.8" />
+    <path
+        android:fillColor="#00000000"
+        android:pathData="M99,0L99,108"
+        android:strokeColor="#33FFFFFF"
+        android:strokeWidth="0.8" />
+    <path
+        android:fillColor="#00000000"
+        android:pathData="M0,9L108,9"
+        android:strokeColor="#33FFFFFF"
+        android:strokeWidth="0.8" />
+    <path
+        android:fillColor="#00000000"
+        android:pathData="M0,19L108,19"
+        android:strokeColor="#33FFFFFF"
+        android:strokeWidth="0.8" />
+    <path
+        android:fillColor="#00000000"
+        android:pathData="M0,29L108,29"
+        android:strokeColor="#33FFFFFF"
+        android:strokeWidth="0.8" />
+    <path
+        android:fillColor="#00000000"
+        android:pathData="M0,39L108,39"
+        android:strokeColor="#33FFFFFF"
+        android:strokeWidth="0.8" />
+    <path
+        android:fillColor="#00000000"
+        android:pathData="M0,49L108,49"
+        android:strokeColor="#33FFFFFF"
+        android:strokeWidth="0.8" />
+    <path
+        android:fillColor="#00000000"
+        android:pathData="M0,59L108,59"
+        android:strokeColor="#33FFFFFF"
+        android:strokeWidth="0.8" />
+    <path
+        android:fillColor="#00000000"
+        android:pathData="M0,69L108,69"
+        android:strokeColor="#33FFFFFF"
+        android:strokeWidth="0.8" />
+    <path
+        android:fillColor="#00000000"
+        android:pathData="M0,79L108,79"
+        android:strokeColor="#33FFFFFF"
+        android:strokeWidth="0.8" />
+    <path
+        android:fillColor="#00000000"
+        android:pathData="M0,89L108,89"
+        android:strokeColor="#33FFFFFF"
+        android:strokeWidth="0.8" />
+    <path
+        android:fillColor="#00000000"
+        android:pathData="M0,99L108,99"
+        android:strokeColor="#33FFFFFF"
+        android:strokeWidth="0.8" />
+    <path
+        android:fillColor="#00000000"
+        android:pathData="M19,29L89,29"
+        android:strokeColor="#33FFFFFF"
+        android:strokeWidth="0.8" />
+    <path
+        android:fillColor="#00000000"
+        android:pathData="M19,39L89,39"
+        android:strokeColor="#33FFFFFF"
+        android:strokeWidth="0.8" />
+    <path
+        android:fillColor="#00000000"
+        android:pathData="M19,49L89,49"
+        android:strokeColor="#33FFFFFF"
+        android:strokeWidth="0.8" />
+    <path
+        android:fillColor="#00000000"
+        android:pathData="M19,59L89,59"
+        android:strokeColor="#33FFFFFF"
+        android:strokeWidth="0.8" />
+    <path
+        android:fillColor="#00000000"
+        android:pathData="M19,69L89,69"
+        android:strokeColor="#33FFFFFF"
+        android:strokeWidth="0.8" />
+    <path
+        android:fillColor="#00000000"
+        android:pathData="M19,79L89,79"
+        android:strokeColor="#33FFFFFF"
+        android:strokeWidth="0.8" />
+    <path
+        android:fillColor="#00000000"
+        android:pathData="M29,19L29,89"
+        android:strokeColor="#33FFFFFF"
+        android:strokeWidth="0.8" />
+    <path
+        android:fillColor="#00000000"
+        android:pathData="M39,19L39,89"
+        android:strokeColor="#33FFFFFF"
+        android:strokeWidth="0.8" />
+    <path
+        android:fillColor="#00000000"
+        android:pathData="M49,19L49,89"
+        android:strokeColor="#33FFFFFF"
+        android:strokeWidth="0.8" />
+    <path
+        android:fillColor="#00000000"
+        android:pathData="M59,19L59,89"
+        android:strokeColor="#33FFFFFF"
+        android:strokeWidth="0.8" />
+    <path
+        android:fillColor="#00000000"
+        android:pathData="M69,19L69,89"
+        android:strokeColor="#33FFFFFF"
+        android:strokeWidth="0.8" />
+    <path
+        android:fillColor="#00000000"
+        android:pathData="M79,19L79,89"
+        android:strokeColor="#33FFFFFF"
+        android:strokeWidth="0.8" />
+</vector>
diff --git a/ui/text/RoundedBackground-Kotlin/app/src/main/res/drawable/rounded.xml b/ui/text/RoundedBackground-Kotlin/app/src/main/res/drawable/rounded.xml
new file mode 100644
index 0000000..f69e6bf
--- /dev/null
+++ b/ui/text/RoundedBackground-Kotlin/app/src/main/res/drawable/rounded.xml
@@ -0,0 +1,21 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  ~ Copyright 2018 The Android Open Source Project
+  ~
+  ~ Licensed under the Apache License, Version 2.0 (the "License");
+  ~ you may not use this file except in compliance with the License.
+  ~ You may obtain a copy of the License at
+  ~
+  ~      http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing, software
+  ~ distributed under the License is distributed on an "AS IS" BASIS,
+  ~ WITHOUT 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="@color/roundedBg"/>
+    <stroke android:width="@dimen/roundedBorderWidth" android:color="@color/roundedBorder"/>
+    <corners android:radius="@dimen/roundedBorderRadius"/>
+</shape>
\ No newline at end of file
diff --git a/ui/text/RoundedBackground-Kotlin/app/src/main/res/drawable/rounded_left.xml b/ui/text/RoundedBackground-Kotlin/app/src/main/res/drawable/rounded_left.xml
new file mode 100644
index 0000000..6883c63
--- /dev/null
+++ b/ui/text/RoundedBackground-Kotlin/app/src/main/res/drawable/rounded_left.xml
@@ -0,0 +1,22 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  ~ Copyright 2018 The Android Open Source Project
+  ~
+  ~ Licensed under the Apache License, Version 2.0 (the "License");
+  ~ you may not use this file except in compliance with the License.
+  ~ You may obtain a copy of the License at
+  ~
+  ~      http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing, software
+  ~ distributed under the License is distributed on an "AS IS" BASIS,
+  ~ WITHOUT 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="@color/roundedBg"/>
+    <stroke android:width="@dimen/roundedBorderWidth" android:color="@color/roundedBorder"/>
+    <corners android:topLeftRadius="@dimen/roundedTextBorderRadius"
+             android:bottomLeftRadius="@dimen/roundedTextBorderRadius"/>
+</shape>
\ No newline at end of file
diff --git a/ui/text/RoundedBackground-Kotlin/app/src/main/res/drawable/rounded_mid.xml b/ui/text/RoundedBackground-Kotlin/app/src/main/res/drawable/rounded_mid.xml
new file mode 100644
index 0000000..b1714ec
--- /dev/null
+++ b/ui/text/RoundedBackground-Kotlin/app/src/main/res/drawable/rounded_mid.xml
@@ -0,0 +1,20 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  ~ Copyright 2018 The Android Open Source Project
+  ~
+  ~ Licensed under the Apache License, Version 2.0 (the "License");
+  ~ you may not use this file except in compliance with the License.
+  ~ You may obtain a copy of the License at
+  ~
+  ~      http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing, software
+  ~ distributed under the License is distributed on an "AS IS" BASIS,
+  ~ WITHOUT 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="@color/roundedBg"/>
+    <stroke android:width="@dimen/roundedBorderWidth" android:color="@color/roundedBorder"/>
+</shape>
\ No newline at end of file
diff --git a/ui/text/RoundedBackground-Kotlin/app/src/main/res/drawable/rounded_right.xml b/ui/text/RoundedBackground-Kotlin/app/src/main/res/drawable/rounded_right.xml
new file mode 100644
index 0000000..8066d97
--- /dev/null
+++ b/ui/text/RoundedBackground-Kotlin/app/src/main/res/drawable/rounded_right.xml
@@ -0,0 +1,22 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  ~ Copyright 2018 The Android Open Source Project
+  ~
+  ~ Licensed under the Apache License, Version 2.0 (the "License");
+  ~ you may not use this file except in compliance with the License.
+  ~ You may obtain a copy of the License at
+  ~
+  ~      http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing, software
+  ~ distributed under the License is distributed on an "AS IS" BASIS,
+  ~ WITHOUT 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="@color/roundedBg"/>
+    <stroke android:width="@dimen/roundedBorderWidth" android:color="@color/roundedBorder"/>
+    <corners android:topRightRadius="@dimen/roundedBorderRadius"
+             android:bottomRightRadius="@dimen/roundedBorderRadius"/>
+</shape>
\ No newline at end of file
diff --git a/ui/text/RoundedBackground-Kotlin/app/src/main/res/layout/activity_main.xml b/ui/text/RoundedBackground-Kotlin/app/src/main/res/layout/activity_main.xml
new file mode 100644
index 0000000..b3738cb
--- /dev/null
+++ b/ui/text/RoundedBackground-Kotlin/app/src/main/res/layout/activity_main.xml
@@ -0,0 +1,131 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  ~ Copyright 2018 The Android Open Source Project
+  ~
+  ~ Licensed under the Apache License, Version 2.0 (the "License");
+  ~ you may not use this file except in compliance with the License.
+  ~ You may obtain a copy of the License at
+  ~
+  ~      http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing, software
+  ~ distributed under the License is distributed on an "AS IS" BASIS,
+  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  ~ See the License for the specific language governing permissions and
+  ~ limitations under the License.
+  -->
+<ScrollView
+        xmlns:android="http://schemas.android.com/apk/res/android"
+        xmlns:tools="http://schemas.android.com/tools"
+        android:layout_width="match_parent"
+        android:layout_height="match_parent" xmlns:app="http://schemas.android.com/apk/res-auto"
+        tools:context=".MainActivity">
+
+    <LinearLayout
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        android:orientation="vertical">
+
+        <com.android.example.text.styling.roundedbg.RoundedBgTextView
+            android:text="@string/ltr"
+            style="@style/Widget.RoundedBackground.SampleTextView"
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:layout_margin="@dimen/textViewMargin"/>
+
+        <View style="@style/Widget.RoundedBackground.Divider"
+            android:layout_width="match_parent"
+            android:layout_height="@dimen/dividerHeight"/>
+
+        <com.android.example.text.styling.roundedbg.RoundedBgTextView
+            android:text="@string/ltr_multi"
+            style="@style/Widget.RoundedBackground.SampleTextView"
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:layout_margin="@dimen/textViewMargin"/>
+
+        <View style="@style/Widget.RoundedBackground.Divider"
+            android:layout_width="match_parent"
+            android:layout_height="@dimen/dividerHeight"/>
+
+        <com.android.example.text.styling.roundedbg.RoundedBgTextView
+            android:text="@string/ltr_long"
+            style="@style/Widget.RoundedBackground.SampleTextView"
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:layout_margin="@dimen/textViewMargin"
+            app:roundedTextDrawable="@drawable/rounded"
+            app:roundedTextDrawableLeft="@drawable/rounded_left"
+            app:roundedTextDrawableMid="@drawable/rounded_mid"
+            app:roundedTextDrawableRight="@drawable/rounded_right"/>
+
+        <View style="@style/Widget.RoundedBackground.Divider"
+            android:layout_width="match_parent"
+            android:layout_height="@dimen/dividerHeight"/>
+
+        <com.android.example.text.styling.roundedbg.RoundedBgTextView
+            android:text="@string/rtl"
+            style="@style/Widget.RoundedBackground.SampleTextView"
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:layout_margin="@dimen/textViewMargin"/>
+
+        <View style="@style/Widget.RoundedBackground.Divider"
+            android:layout_width="match_parent"
+            android:layout_height="@dimen/dividerHeight"/>
+
+        <com.android.example.text.styling.roundedbg.RoundedBgTextView
+            android:text="@string/rtl_multi"
+            style="@style/Widget.RoundedBackground.SampleTextView"
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:layout_margin="@dimen/textViewMargin"/>
+
+        <View style="@style/Widget.RoundedBackground.Divider"
+            android:layout_width="match_parent"
+            android:layout_height="@dimen/dividerHeight"/>
+
+        <com.android.example.text.styling.roundedbg.RoundedBgTextView
+            android:text="@string/lang1"
+            style="@style/Widget.RoundedBackground.SampleTextView"
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:layout_margin="@dimen/textViewMargin"/>
+
+        <View style="@style/Widget.RoundedBackground.Divider"
+            android:layout_width="match_parent"
+            android:layout_height="@dimen/dividerHeight"/>
+
+        <com.android.example.text.styling.roundedbg.RoundedBgTextView
+            android:text="@string/lang2"
+            style="@style/Widget.RoundedBackground.SampleTextView"
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:layout_margin="@dimen/textViewMargin"/>
+
+        <View style="@style/Widget.RoundedBackground.Divider"
+            android:layout_width="match_parent"
+            android:layout_height="@dimen/dividerHeight"/>
+
+        <com.android.example.text.styling.roundedbg.RoundedBgTextView
+            android:text="@string/lang3"
+            style="@style/Widget.RoundedBackground.SampleTextView"
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:layout_margin="@dimen/textViewMargin"/>
+
+        <View style="@style/Widget.RoundedBackground.Divider"
+            android:layout_width="match_parent"
+            android:layout_height="@dimen/dividerHeight"/>
+
+        <com.android.example.text.styling.roundedbg.RoundedBgTextView
+            android:text="@string/lang4"
+            style="@style/Widget.RoundedBackground.SampleTextView"
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:layout_margin="@dimen/textViewMargin"/>
+
+    </LinearLayout>
+
+
+</ScrollView>
diff --git a/ui/text/RoundedBackground-Kotlin/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml b/ui/text/RoundedBackground-Kotlin/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml
new file mode 100644
index 0000000..d3441ca
--- /dev/null
+++ b/ui/text/RoundedBackground-Kotlin/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml
@@ -0,0 +1,20 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  ~ Copyright 2018 The Android Open Source Project
+  ~
+  ~ Licensed under the Apache License, Version 2.0 (the "License");
+  ~ you may not use this file except in compliance with the License.
+  ~ You may obtain a copy of the License at
+  ~
+  ~      http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing, software
+  ~ distributed under the License is distributed on an "AS IS" BASIS,
+  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  ~ See the License for the specific language governing permissions and
+  ~ limitations under the License.
+  -->
+<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
+    <background android:drawable="@drawable/ic_launcher_background" />
+    <foreground android:drawable="@drawable/ic_launcher_foreground" />
+</adaptive-icon>
\ No newline at end of file
diff --git a/ui/text/RoundedBackground-Kotlin/app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml b/ui/text/RoundedBackground-Kotlin/app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml
new file mode 100644
index 0000000..d3441ca
--- /dev/null
+++ b/ui/text/RoundedBackground-Kotlin/app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml
@@ -0,0 +1,20 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  ~ Copyright 2018 The Android Open Source Project
+  ~
+  ~ Licensed under the Apache License, Version 2.0 (the "License");
+  ~ you may not use this file except in compliance with the License.
+  ~ You may obtain a copy of the License at
+  ~
+  ~      http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing, software
+  ~ distributed under the License is distributed on an "AS IS" BASIS,
+  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  ~ See the License for the specific language governing permissions and
+  ~ limitations under the License.
+  -->
+<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
+    <background android:drawable="@drawable/ic_launcher_background" />
+    <foreground android:drawable="@drawable/ic_launcher_foreground" />
+</adaptive-icon>
\ No newline at end of file
diff --git a/ui/text/RoundedBackground-Kotlin/app/src/main/res/mipmap-hdpi/ic_launcher.png b/ui/text/RoundedBackground-Kotlin/app/src/main/res/mipmap-hdpi/ic_launcher.png
new file mode 100644
index 0000000..a2f5908
--- /dev/null
+++ b/ui/text/RoundedBackground-Kotlin/app/src/main/res/mipmap-hdpi/ic_launcher.png
Binary files differ
diff --git a/ui/text/RoundedBackground-Kotlin/app/src/main/res/mipmap-hdpi/ic_launcher_round.png b/ui/text/RoundedBackground-Kotlin/app/src/main/res/mipmap-hdpi/ic_launcher_round.png
new file mode 100644
index 0000000..1b52399
--- /dev/null
+++ b/ui/text/RoundedBackground-Kotlin/app/src/main/res/mipmap-hdpi/ic_launcher_round.png
Binary files differ
diff --git a/ui/text/RoundedBackground-Kotlin/app/src/main/res/mipmap-mdpi/ic_launcher.png b/ui/text/RoundedBackground-Kotlin/app/src/main/res/mipmap-mdpi/ic_launcher.png
new file mode 100644
index 0000000..ff10afd
--- /dev/null
+++ b/ui/text/RoundedBackground-Kotlin/app/src/main/res/mipmap-mdpi/ic_launcher.png
Binary files differ
diff --git a/ui/text/RoundedBackground-Kotlin/app/src/main/res/mipmap-mdpi/ic_launcher_round.png b/ui/text/RoundedBackground-Kotlin/app/src/main/res/mipmap-mdpi/ic_launcher_round.png
new file mode 100644
index 0000000..115a4c7
--- /dev/null
+++ b/ui/text/RoundedBackground-Kotlin/app/src/main/res/mipmap-mdpi/ic_launcher_round.png
Binary files differ
diff --git a/ui/text/RoundedBackground-Kotlin/app/src/main/res/mipmap-xhdpi/ic_launcher.png b/ui/text/RoundedBackground-Kotlin/app/src/main/res/mipmap-xhdpi/ic_launcher.png
new file mode 100644
index 0000000..dcd3cd8
--- /dev/null
+++ b/ui/text/RoundedBackground-Kotlin/app/src/main/res/mipmap-xhdpi/ic_launcher.png
Binary files differ
diff --git a/ui/text/RoundedBackground-Kotlin/app/src/main/res/mipmap-xhdpi/ic_launcher_round.png b/ui/text/RoundedBackground-Kotlin/app/src/main/res/mipmap-xhdpi/ic_launcher_round.png
new file mode 100644
index 0000000..459ca60
--- /dev/null
+++ b/ui/text/RoundedBackground-Kotlin/app/src/main/res/mipmap-xhdpi/ic_launcher_round.png
Binary files differ
diff --git a/ui/text/RoundedBackground-Kotlin/app/src/main/res/mipmap-xxhdpi/ic_launcher.png b/ui/text/RoundedBackground-Kotlin/app/src/main/res/mipmap-xxhdpi/ic_launcher.png
new file mode 100644
index 0000000..8ca12fe
--- /dev/null
+++ b/ui/text/RoundedBackground-Kotlin/app/src/main/res/mipmap-xxhdpi/ic_launcher.png
Binary files differ
diff --git a/ui/text/RoundedBackground-Kotlin/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png b/ui/text/RoundedBackground-Kotlin/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png
new file mode 100644
index 0000000..8e19b41
--- /dev/null
+++ b/ui/text/RoundedBackground-Kotlin/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png
Binary files differ
diff --git a/ui/text/RoundedBackground-Kotlin/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png b/ui/text/RoundedBackground-Kotlin/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png
new file mode 100644
index 0000000..b824ebd
--- /dev/null
+++ b/ui/text/RoundedBackground-Kotlin/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png
Binary files differ
diff --git a/ui/text/RoundedBackground-Kotlin/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png b/ui/text/RoundedBackground-Kotlin/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png
new file mode 100644
index 0000000..4c19a13
--- /dev/null
+++ b/ui/text/RoundedBackground-Kotlin/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png
Binary files differ
diff --git a/ui/text/RoundedBackground-Kotlin/app/src/main/res/values/colors.xml b/ui/text/RoundedBackground-Kotlin/app/src/main/res/values/colors.xml
new file mode 100644
index 0000000..932c9e9
--- /dev/null
+++ b/ui/text/RoundedBackground-Kotlin/app/src/main/res/values/colors.xml
@@ -0,0 +1,26 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  ~ Copyright 2018 The Android Open Source Project
+  ~
+  ~ Licensed under the Apache License, Version 2.0 (the "License");
+  ~ you may not use this file except in compliance with the License.
+  ~ You may obtain a copy of the License at
+  ~
+  ~      http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing, software
+  ~ distributed under the License is distributed on an "AS IS" BASIS,
+  ~ WITHOUT 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">#DDD</color>
+    <color name="colorPrimaryDark">#CCC</color>
+    <color name="colorAccent">#FF7F50</color>
+
+    <color name="divider">#CCC</color>
+
+    <color name="roundedBg">#FFF176</color>
+    <color name="roundedBorder">#CABF45</color>
+</resources>
diff --git a/ui/text/RoundedBackground-Kotlin/app/src/main/res/values/dimens.xml b/ui/text/RoundedBackground-Kotlin/app/src/main/res/values/dimens.xml
new file mode 100644
index 0000000..02814de
--- /dev/null
+++ b/ui/text/RoundedBackground-Kotlin/app/src/main/res/values/dimens.xml
@@ -0,0 +1,23 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  ~ Copyright 2018 The Android Open Source Project
+  ~
+  ~ Licensed under the Apache License, Version 2.0 (the "License");
+  ~ you may not use this file except in compliance with the License.
+  ~ You may obtain a copy of the License at
+  ~
+  ~      http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing, software
+  ~ distributed under the License is distributed on an "AS IS" BASIS,
+  ~ WITHOUT 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="textViewMargin">8dp</dimen>
+    <dimen name="dividerHeight">1dp</dimen>
+
+    <dimen name="roundedBorderRadius">6dp</dimen>
+    <dimen name="roundedBorderWidth">2dp</dimen>
+</resources>
diff --git a/ui/text/RoundedBackground-Kotlin/app/src/main/res/values/strings.xml b/ui/text/RoundedBackground-Kotlin/app/src/main/res/values/strings.xml
new file mode 100644
index 0000000..35cc087
--- /dev/null
+++ b/ui/text/RoundedBackground-Kotlin/app/src/main/res/values/strings.xml
@@ -0,0 +1,32 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  ~ Copyright 2018 The Android Open Source Project
+  ~
+  ~ Licensed under the Apache License, Version 2.0 (the "License");
+  ~ you may not use this file except in compliance with the License.
+  ~ You may obtain a copy of the License at
+  ~
+  ~      http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing, software
+  ~ distributed under the License is distributed on an "AS IS" BASIS,
+  ~ WITHOUT 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">Rounded Multiline Bg</string>
+
+    <!-- Without the quotes at the begining and end Android strips the whitespace and also starts
+    the annotation at the wrong position. -->
+    <string name="ltr">"this is <annotation key="rounded">a regular</annotation> paragraph."</string>
+    <string name="ltr_multi">"this contains <annotation key="rounded">a\nline break</annotation> and continues."</string>
+    <string name="ltr_long">"this is <annotation key="rounded">a paragraph \nthat would cover more than\ntwo</annotation> lines."</string>
+    <string name="rtl"> هذا هو "<annotation key="rounded">الحق في</annotation>" الفقرة اليسرى </string>
+    <string name="rtl_multi"> هذا هو"<annotation key="rounded">حق\n لفقرة</annotation>" اليسار </string>
+    <string name="lang1">"ဟ <annotation key="rounded">ယ်လို\nဟယ်လို</annotation> ယ်လို"</string>
+    <string name="lang2">"អ <annotation key="rounded">៊ីតាលី\nអ៊ីតា</annotation> លី""</string>
+    <string name="lang3">"नमस्ते <annotation key="rounded">दुनिया\nनमस्ते</annotation> दुनिया"</string>
+    <string name="lang4">"สวัสดี <annotation key="rounded">ชาวโลก\nสวัสดีชาวโ</annotation> ลก"</string>
+
+</resources>
\ No newline at end of file
diff --git a/ui/text/RoundedBackground-Kotlin/app/src/main/res/values/styles.xml b/ui/text/RoundedBackground-Kotlin/app/src/main/res/values/styles.xml
new file mode 100644
index 0000000..1997dad
--- /dev/null
+++ b/ui/text/RoundedBackground-Kotlin/app/src/main/res/values/styles.xml
@@ -0,0 +1,42 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  ~ Copyright 2018 The Android Open Source Project
+  ~
+  ~ Licensed under the Apache License, Version 2.0 (the "License");
+  ~ you may not use this file except in compliance with the License.
+  ~ You may obtain a copy of the License at
+  ~
+  ~      http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing, software
+  ~ distributed under the License is distributed on an "AS IS" BASIS,
+  ~ WITHOUT 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="Theme.RoundedBackground" parent="Theme.AppCompat.Light">
+        <item name="colorPrimary">@color/colorPrimary</item>
+        <item name="colorPrimaryDark">@color/colorPrimaryDark</item>
+        <item name="colorAccent">@color/colorAccent</item>
+    </style>
+
+    <style name="Widget.RoundedBackground" parent="android:Widget"/>
+
+    <style name="Widget.RoundedBackground.SampleTextView">
+        <item name="android:lineSpacingExtra">2dp</item>
+        <item name="android:lineSpacingMultiplier">1.5</item>
+        <item name="android:padding">4dp</item>
+        <item name="android:textSize">18sp</item>
+        <item name="android:includeFontPadding">true</item>
+        <item name="roundedTextHorizontalPadding">2dp</item>
+        <item name="roundedTextVerticalPadding">2dp</item>
+    </style>
+
+    <style name="Widget.RoundedBackground.Divider">
+        <item name="android:background">#CCC</item>
+        <item name="android:padding">2dp</item>
+    </style>
+</resources>
diff --git a/ui/text/RoundedBackground-Kotlin/build.gradle b/ui/text/RoundedBackground-Kotlin/build.gradle
new file mode 100644
index 0000000..f6df756
--- /dev/null
+++ b/ui/text/RoundedBackground-Kotlin/build.gradle
@@ -0,0 +1,49 @@
+/*
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+// Top-level build file where you can add configuration options common to all sub-projects/modules.
+
+buildscript {
+    ext.kotlin_version = '1.2.51'
+    repositories {
+        google()
+        mavenCentral()
+    }
+    dependencies {
+        classpath 'com.android.tools.build:gradle:3.3.0-alpha03'
+        classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
+    }
+}
+
+allprojects {
+    repositories {
+        google()
+        mavenCentral()
+    }
+}
+
+task clean(type: Delete) {
+    delete rootProject.buildDir
+}
+
+ext {
+    // Sdk and tools
+    minSdkVersion = 15
+    targetSdkVersion = 28
+    compileSdkVersion = 28
+    // App dependencies
+    androidxVersion = '1.0.0-beta01'
+    ktxVersion = '1.0.0-alpha1'
+}
\ No newline at end of file
diff --git a/ui/text/RoundedBackground-Kotlin/gradle.properties b/ui/text/RoundedBackground-Kotlin/gradle.properties
new file mode 100644
index 0000000..743d692
--- /dev/null
+++ b/ui/text/RoundedBackground-Kotlin/gradle.properties
@@ -0,0 +1,13 @@
+# 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/ui/text/RoundedBackground-Kotlin/gradle/wrapper/gradle-wrapper.jar b/ui/text/RoundedBackground-Kotlin/gradle/wrapper/gradle-wrapper.jar
new file mode 100644
index 0000000..7a3265e
--- /dev/null
+++ b/ui/text/RoundedBackground-Kotlin/gradle/wrapper/gradle-wrapper.jar
Binary files differ
diff --git a/ui/text/RoundedBackground-Kotlin/gradle/wrapper/gradle-wrapper.properties b/ui/text/RoundedBackground-Kotlin/gradle/wrapper/gradle-wrapper.properties
new file mode 100644
index 0000000..37fcd35
--- /dev/null
+++ b/ui/text/RoundedBackground-Kotlin/gradle/wrapper/gradle-wrapper.properties
@@ -0,0 +1,6 @@
+#Wed Jul 11 10:47:22 PDT 2018
+distributionBase=GRADLE_USER_HOME
+distributionPath=wrapper/dists
+zipStoreBase=GRADLE_USER_HOME
+zipStorePath=wrapper/dists
+distributionUrl=https\://services.gradle.org/distributions/gradle-4.9-bin.zip
diff --git a/ui/text/RoundedBackground-Kotlin/gradlew b/ui/text/RoundedBackground-Kotlin/gradlew
new file mode 100755
index 0000000..cccdd3d
--- /dev/null
+++ b/ui/text/RoundedBackground-Kotlin/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 run from Finder on Mac, the cwd is wrong
+if [ "$(uname)" = "Darwin" ] && [ "$HOME" = "$PWD" ]; then
+  cd "$(dirname "$0")"
+fi
+
+exec "$JAVACMD" "$@"
diff --git a/ui/text/RoundedBackground-Kotlin/gradlew.bat b/ui/text/RoundedBackground-Kotlin/gradlew.bat
new file mode 100644
index 0000000..e95643d
--- /dev/null
+++ b/ui/text/RoundedBackground-Kotlin/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/ui/text/RoundedBackground-Kotlin/lib/.gitignore b/ui/text/RoundedBackground-Kotlin/lib/.gitignore
new file mode 100644
index 0000000..796b96d
--- /dev/null
+++ b/ui/text/RoundedBackground-Kotlin/lib/.gitignore
@@ -0,0 +1 @@
+/build
diff --git a/ui/text/RoundedBackground-Kotlin/lib/build.gradle b/ui/text/RoundedBackground-Kotlin/lib/build.gradle
new file mode 100644
index 0000000..6c49986
--- /dev/null
+++ b/ui/text/RoundedBackground-Kotlin/lib/build.gradle
@@ -0,0 +1,43 @@
+/*
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+apply plugin: 'com.android.library'
+
+apply plugin: 'kotlin-android'
+
+android {
+    compileSdkVersion rootProject.compileSdkVersion
+
+    defaultConfig {
+        minSdkVersion rootProject.minSdkVersion
+        targetSdkVersion rootProject.targetSdkVersion
+        versionCode 1
+        versionName "1.0"
+    }
+
+    buildTypes {
+        release {
+            minifyEnabled false
+            proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
+        }
+    }
+
+}
+
+dependencies {
+    implementation "androidx.core:core-ktx:${ktxVersion}"
+    implementation "androidx.appcompat:appcompat:$androidxVersion"
+    implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version"
+}
\ No newline at end of file
diff --git a/ui/text/RoundedBackground-Kotlin/lib/src/main/AndroidManifest.xml b/ui/text/RoundedBackground-Kotlin/lib/src/main/AndroidManifest.xml
new file mode 100644
index 0000000..43a5eb0
--- /dev/null
+++ b/ui/text/RoundedBackground-Kotlin/lib/src/main/AndroidManifest.xml
@@ -0,0 +1,18 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  ~ Copyright 2018 The Android Open Source Project
+  ~
+  ~ Licensed under the Apache License, Version 2.0 (the "License");
+  ~ you may not use this file except in compliance with the License.
+  ~ You may obtain a copy of the License at
+  ~
+  ~      http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing, software
+  ~ distributed under the License is distributed on an "AS IS" BASIS,
+  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  ~ See the License for the specific language governing permissions and
+  ~ limitations under the License.
+  -->
+<manifest
+    package="com.android.example.text.styling.roundedbg"/>
diff --git a/ui/text/RoundedBackground-Kotlin/lib/src/main/java/com/android/example/text/styling/roundedbg/LayoutExtensions.kt b/ui/text/RoundedBackground-Kotlin/lib/src/main/java/com/android/example/text/styling/roundedbg/LayoutExtensions.kt
new file mode 100644
index 0000000..f7478eb
--- /dev/null
+++ b/ui/text/RoundedBackground-Kotlin/lib/src/main/java/com/android/example/text/styling/roundedbg/LayoutExtensions.kt
@@ -0,0 +1,91 @@
+/*
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.example.text.styling.roundedbg
+
+import android.os.Build
+import android.text.Layout
+
+// Extension functions for Layout object
+
+/**
+ * Android system default line spacing extra
+ */
+private const val DEFAULT_LINESPACING_EXTRA = 0f
+
+/**
+ * Android system default line spacing multiplier
+ */
+private const val DEFAULT_LINESPACING_MULTIPLIER = 1f
+
+/**
+ * Get the line bottom discarding the line spacing added.
+ */
+fun Layout.getLineBottomWithoutSpacing(line: Int): Int {
+    val lineBottom = getLineBottom(line)
+    val lastLineSpacingNotAdded = Build.VERSION.SDK_INT >= 19
+    val isLastLine = line == lineCount - 1
+
+    val lineBottomWithoutSpacing: Int
+    val lineSpacingExtra = spacingAdd
+    val lineSpacingMultiplier = spacingMultiplier
+    val hasLineSpacing = lineSpacingExtra != DEFAULT_LINESPACING_EXTRA
+        || lineSpacingMultiplier != DEFAULT_LINESPACING_MULTIPLIER
+
+    if (!hasLineSpacing || isLastLine && lastLineSpacingNotAdded) {
+        lineBottomWithoutSpacing = lineBottom
+    } else {
+        val extra: Float
+        if (lineSpacingMultiplier.compareTo(DEFAULT_LINESPACING_MULTIPLIER) != 0) {
+            val lineHeight = getLineHeight(line)
+            extra = lineHeight - (lineHeight - lineSpacingExtra) / lineSpacingMultiplier
+        } else {
+            extra = lineSpacingExtra
+        }
+
+        lineBottomWithoutSpacing = (lineBottom - extra).toInt()
+    }
+
+    return lineBottomWithoutSpacing
+}
+
+/**
+ * Get the line height of a line.
+ */
+fun Layout.getLineHeight(line: Int): Int {
+    return getLineTop(line + 1) - getLineTop(line)
+}
+
+/**
+ * Returns the top of the Layout after removing the extra padding applied by  the Layout.
+ */
+fun Layout.getLineTopWithoutPadding(line: Int): Int {
+    var lineTop = getLineTop(line)
+    if (line == 0) {
+        lineTop -= topPadding
+    }
+    return lineTop
+}
+
+/**
+ * Returns the bottom of the Layout after removing the extra padding applied by the Layout.
+ */
+fun Layout.getLineBottomWithoutPadding(line: Int): Int {
+    var lineBottom = getLineBottomWithoutSpacing(line)
+    if (line == lineCount - 1) {
+        lineBottom -= bottomPadding
+    }
+    return lineBottom
+}
\ No newline at end of file
diff --git a/ui/text/RoundedBackground-Kotlin/lib/src/main/java/com/android/example/text/styling/roundedbg/RoundedBgTextView.kt b/ui/text/RoundedBackground-Kotlin/lib/src/main/java/com/android/example/text/styling/roundedbg/RoundedBgTextView.kt
new file mode 100644
index 0000000..9f0d288
--- /dev/null
+++ b/ui/text/RoundedBackground-Kotlin/lib/src/main/java/com/android/example/text/styling/roundedbg/RoundedBgTextView.kt
@@ -0,0 +1,61 @@
+/*
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.example.text.styling.roundedbg
+
+import android.content.Context
+import android.graphics.Canvas
+import android.text.Spanned
+import android.util.AttributeSet
+import androidx.appcompat.widget.AppCompatTextView
+import androidx.core.graphics.withTranslation
+
+/**
+ * A TextView that can draw rounded background to the portions of the text. See
+ * [TextRoundedBgHelper] for more information.
+ *
+ * See [TextRoundedBgAttributeReader] for supported attributes.
+ */
+class RoundedBgTextView : AppCompatTextView {
+
+    private val textRoundedBgHelper: TextRoundedBgHelper
+
+    @JvmOverloads
+    constructor(
+        context: Context,
+        attrs: AttributeSet? = null,
+        defStyleAttr: Int = android.R.attr.textViewStyle
+    ) : super(context, attrs, defStyleAttr) {
+        val attributeReader = TextRoundedBgAttributeReader(context, attrs)
+        textRoundedBgHelper = TextRoundedBgHelper(
+            horizontalPadding = attributeReader.horizontalPadding,
+            verticalPadding = attributeReader.verticalPadding,
+            drawable = attributeReader.drawable,
+            drawableLeft = attributeReader.drawableLeft,
+            drawableMid = attributeReader.drawableMid,
+            drawableRight = attributeReader.drawableRight
+        )
+    }
+
+    override fun onDraw(canvas: Canvas) {
+        // need to draw bg first so that text can be on top during super.onDraw()
+        if (text is Spanned && layout != null) {
+            canvas.withTranslation(totalPaddingLeft.toFloat(), totalPaddingTop.toFloat()) {
+                textRoundedBgHelper.draw(canvas, text as Spanned, layout)
+            }
+        }
+        super.onDraw(canvas)
+    }
+}
\ No newline at end of file
diff --git a/ui/text/RoundedBackground-Kotlin/lib/src/main/java/com/android/example/text/styling/roundedbg/TextRoundedBgAttributeReader.kt b/ui/text/RoundedBackground-Kotlin/lib/src/main/java/com/android/example/text/styling/roundedbg/TextRoundedBgAttributeReader.kt
new file mode 100644
index 0000000..5ecc41d
--- /dev/null
+++ b/ui/text/RoundedBackground-Kotlin/lib/src/main/java/com/android/example/text/styling/roundedbg/TextRoundedBgAttributeReader.kt
@@ -0,0 +1,72 @@
+/*
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+*/
+package com.android.example.text.styling.roundedbg
+
+import android.content.Context
+import android.graphics.drawable.Drawable
+import android.util.AttributeSet
+import androidx.core.content.res.getDrawableOrThrow
+
+/**
+ * Reads default attributes that [TextRoundedBgHelper] needs from resources. The attributes read
+ * are:
+ *
+ * - chHorizontalPadding: the padding to be applied to left & right of the background
+ * - chVerticalPadding: the padding to be applied to top & bottom of the background
+ * - chDrawable: the drawable used to draw the background
+ * - chDrawableLeft: the drawable used to draw left edge of the background
+ * - chDrawableMid: the drawable used to draw for whole line
+ * - chDrawableRight: the drawable used to draw right edge of the background
+ */
+class TextRoundedBgAttributeReader(context: Context, attrs: AttributeSet?) {
+
+    val horizontalPadding: Int
+    val verticalPadding: Int
+    val drawable: Drawable
+    val drawableLeft: Drawable
+    val drawableMid: Drawable
+    val drawableRight: Drawable
+
+    init {
+        val typedArray = context.obtainStyledAttributes(
+            attrs,
+            R.styleable.TextRoundedBgHelper,
+            0,
+            R.style.RoundedBgTextView
+        )
+        horizontalPadding = typedArray.getDimensionPixelSize(
+            R.styleable.TextRoundedBgHelper_roundedTextHorizontalPadding,
+            0
+        )
+        verticalPadding = typedArray.getDimensionPixelSize(
+            R.styleable.TextRoundedBgHelper_roundedTextVerticalPadding,
+            0
+        )
+        drawable = typedArray.getDrawableOrThrow(
+            R.styleable.TextRoundedBgHelper_roundedTextDrawable
+        )
+        drawableLeft = typedArray.getDrawableOrThrow(
+            R.styleable.TextRoundedBgHelper_roundedTextDrawableLeft
+        )
+        drawableMid = typedArray.getDrawableOrThrow(
+            R.styleable.TextRoundedBgHelper_roundedTextDrawableMid
+        )
+        drawableRight = typedArray.getDrawableOrThrow(
+            R.styleable.TextRoundedBgHelper_roundedTextDrawableRight
+        )
+        typedArray.recycle()
+    }
+}
diff --git a/ui/text/RoundedBackground-Kotlin/lib/src/main/java/com/android/example/text/styling/roundedbg/TextRoundedBgHelper.kt b/ui/text/RoundedBackground-Kotlin/lib/src/main/java/com/android/example/text/styling/roundedbg/TextRoundedBgHelper.kt
new file mode 100644
index 0000000..262a5fe
--- /dev/null
+++ b/ui/text/RoundedBackground-Kotlin/lib/src/main/java/com/android/example/text/styling/roundedbg/TextRoundedBgHelper.kt
@@ -0,0 +1,102 @@
+/*
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+*/
+package com.android.example.text.styling.roundedbg
+
+import android.graphics.Canvas
+import android.graphics.drawable.Drawable
+import android.text.Annotation
+import android.text.Layout
+import android.text.Spanned
+
+/**
+ * Helper class to draw multi-line rounded background to certain parts of a text. The start/end
+ * positions of the backgrounds are annotated with [android.text.Annotation] class. Each annotation
+ * should have the annotation key set to **rounded**.
+ *
+ * i.e.:
+ * ```
+ *    <!--without the quotes at the begining and end Android strips the whitespace and also starts
+ *        the annotation at the wrong position-->
+ *    <string name="ltr">"this is <annotation key="rounded">a regular</annotation> paragraph."</string>
+ * ```
+ *
+ * **Note:** BiDi text is not supported.
+ *
+ * @param horizontalPadding the padding to be applied to left & right of the background
+ * @param verticalPadding the padding to be applied to top & bottom of the background
+ * @param drawable the drawable used to draw the background
+ * @param drawableLeft the drawable used to draw left edge of the background
+ * @param drawableMid the drawable used to draw for whole line
+ * @param drawableRight the drawable used to draw right edge of the background
+ */
+class TextRoundedBgHelper(
+    val horizontalPadding: Int,
+    verticalPadding: Int,
+    drawable: Drawable,
+    drawableLeft: Drawable,
+    drawableMid: Drawable,
+    drawableRight: Drawable
+) {
+
+    private val singleLineRenderer: TextRoundedBgRenderer by lazy {
+        SingleLineRenderer(
+            horizontalPadding = horizontalPadding,
+            verticalPadding = verticalPadding,
+            drawable = drawable
+        )
+    }
+
+    private val multiLineRenderer: TextRoundedBgRenderer by lazy {
+        MultiLineRenderer(
+            horizontalPadding = horizontalPadding,
+            verticalPadding = verticalPadding,
+            drawableLeft = drawableLeft,
+            drawableMid = drawableMid,
+            drawableRight = drawableRight
+        )
+    }
+
+    /**
+     * Call this function during onDraw of another widget such as TextView.
+     *
+     * @param canvas Canvas to draw onto
+     * @param text
+     * @param layout Layout that contains the text
+     */
+    fun draw(canvas: Canvas, text: Spanned, layout: Layout) {
+        // ideally the calculations here should be cached since they are not cheap. However, proper
+        // invalidation of the cache is required whenever anything related to text has changed.
+        val spans = text.getSpans(0, text.length, Annotation::class.java)
+        spans.forEach { span ->
+            if (span.value.equals("rounded")) {
+                val spanStart = text.getSpanStart(span)
+                val spanEnd = text.getSpanEnd(span)
+                val startLine = layout.getLineForOffset(spanStart)
+                val endLine = layout.getLineForOffset(spanEnd)
+
+                // start can be on the left or on the right depending on the language direction.
+                val startOffset = (layout.getPrimaryHorizontal(spanStart)
+                    + -1 * layout.getParagraphDirection(startLine) * horizontalPadding).toInt()
+                // end can be on the left or on the right depending on the language direction.
+                val endOffset = (layout.getPrimaryHorizontal(spanEnd)
+                    + layout.getParagraphDirection(endLine) * horizontalPadding).toInt()
+
+                val renderer = if (startLine == endLine) singleLineRenderer else multiLineRenderer
+                renderer.draw(canvas, layout, startLine, endLine, startOffset, endOffset)
+            }
+        }
+    }
+}
\ No newline at end of file
diff --git a/ui/text/RoundedBackground-Kotlin/lib/src/main/java/com/android/example/text/styling/roundedbg/TextRoundedBgRenderer.kt b/ui/text/RoundedBackground-Kotlin/lib/src/main/java/com/android/example/text/styling/roundedbg/TextRoundedBgRenderer.kt
new file mode 100644
index 0000000..37d814a
--- /dev/null
+++ b/ui/text/RoundedBackground-Kotlin/lib/src/main/java/com/android/example/text/styling/roundedbg/TextRoundedBgRenderer.kt
@@ -0,0 +1,209 @@
+/*
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+*/
+package com.android.example.text.styling.roundedbg
+
+import android.graphics.Canvas
+import android.graphics.drawable.Drawable
+import android.text.Layout
+import kotlin.math.max
+import kotlin.math.min
+
+/**
+ * Base class for single and multi line rounded background renderers.
+ *
+ * @param horizontalPadding the padding to be applied to left & right of the background
+ * @param verticalPadding the padding to be applied to top & bottom of the background
+ */
+internal abstract class TextRoundedBgRenderer(
+        val horizontalPadding: Int,
+        val verticalPadding: Int
+) {
+
+    /**
+     * Draw the background that starts at the {@code startOffset} and ends at {@code endOffset}.
+     *
+     * @param canvas Canvas to draw onto
+     * @param layout Layout that contains the text
+     * @param startLine the start line for the background
+     * @param endLine the end line for the background
+     * @param startOffset the character offset that the background should start at
+     * @param endOffset the character offset that the background should end at
+     */
+    abstract fun draw(
+        canvas: Canvas,
+        layout: Layout,
+        startLine: Int,
+        endLine: Int,
+        startOffset: Int,
+        endOffset: Int
+    )
+
+    /**
+     * Get the top offset of the line and add padding into account so that there is a gap between
+     * top of the background and top of the text.
+     *
+     * @param layout Layout object that contains the text
+     * @param line line number
+     */
+    protected fun getLineTop(layout: Layout, line: Int): Int {
+        return layout.getLineTopWithoutPadding(line) - verticalPadding
+    }
+
+    /**
+     * Get the bottom offset of the line and add padding into account so that there is a gap between
+     * bottom of the background and bottom of the text.
+     *
+     * @param layout Layout object that contains the text
+     * @param line line number
+     */
+    protected fun getLineBottom(layout: Layout, line: Int): Int {
+        return layout.getLineBottomWithoutPadding(line) + verticalPadding
+    }
+}
+
+/**
+ * Draws the background for text that starts and ends on the same line.
+ *
+ * @param horizontalPadding the padding to be applied to left & right of the background
+ * @param verticalPadding the padding to be applied to top & bottom of the background
+ * @param drawable the drawable used to draw the background
+ */
+internal class SingleLineRenderer(
+    horizontalPadding: Int,
+    verticalPadding: Int,
+    val drawable: Drawable
+) : TextRoundedBgRenderer(horizontalPadding, verticalPadding) {
+
+    override fun draw(
+        canvas: Canvas,
+        layout: Layout,
+        startLine: Int,
+        endLine: Int,
+        startOffset: Int,
+        endOffset: Int
+    ) {
+        val lineTop = getLineTop(layout, startLine)
+        val lineBottom = getLineBottom(layout, startLine)
+        // get min of start/end for left, and max of start/end for right since we don't
+        // the language direction
+        val left = min(startOffset, endOffset)
+        val right = max(startOffset, endOffset)
+        drawable.setBounds(left, lineTop, right, lineBottom)
+        drawable.draw(canvas)
+    }
+}
+
+/**
+ * Draws the background for text that starts and ends on different lines.
+ *
+ * @param horizontalPadding the padding to be applied to left & right of the background
+ * @param verticalPadding the padding to be applied to top & bottom of the background
+ * @param drawableLeft the drawable used to draw left edge of the background
+ * @param drawableMid the drawable used to draw for whole line
+ * @param drawableRight the drawable used to draw right edge of the background
+ */
+internal class MultiLineRenderer(
+    horizontalPadding: Int,
+    verticalPadding: Int,
+    val drawableLeft: Drawable,
+    val drawableMid: Drawable,
+    val drawableRight: Drawable
+) : TextRoundedBgRenderer(horizontalPadding, verticalPadding) {
+
+    override fun draw(
+        canvas: Canvas,
+        layout: Layout,
+        startLine: Int,
+        endLine: Int,
+        startOffset: Int,
+        endOffset: Int
+    ) {
+        // draw the first line
+        val paragDir = layout.getParagraphDirection(startLine)
+        val lineEndOffset = if (paragDir == Layout.DIR_RIGHT_TO_LEFT) {
+            layout.getLineLeft(startLine) - horizontalPadding
+        } else {
+            layout.getLineRight(startLine) + horizontalPadding
+        }.toInt()
+
+        var lineBottom = getLineBottom(layout, startLine)
+        var lineTop = getLineTop(layout, startLine)
+        drawStart(canvas, startOffset, lineTop, lineEndOffset, lineBottom)
+
+        // for the lines in the middle draw the mid drawable
+        for (line in startLine + 1 until endLine) {
+            lineTop = getLineTop(layout, line)
+            lineBottom = getLineBottom(layout, line)
+            drawableMid.setBounds(
+                (layout.getLineLeft(line).toInt() - horizontalPadding),
+                lineTop,
+                (layout.getLineRight(line).toInt() + horizontalPadding),
+                lineBottom
+            )
+            drawableMid.draw(canvas)
+        }
+
+        val lineStartOffset = if (paragDir == Layout.DIR_RIGHT_TO_LEFT) {
+            layout.getLineRight(startLine) + horizontalPadding
+        } else {
+            layout.getLineLeft(startLine) - horizontalPadding
+        }.toInt()
+
+        // draw the last line
+        lineBottom = getLineBottom(layout, endLine)
+        lineTop = getLineTop(layout, endLine)
+
+        drawEnd(canvas, lineStartOffset, lineTop, endOffset, lineBottom)
+    }
+
+    /**
+     * Draw the first line of a multiline annotation. Handles LTR/RTL.
+     *
+     * @param canvas Canvas to draw onto
+     * @param start start coordinate for the background
+     * @param top top coordinate for the background
+     * @param end end coordinate for the background
+     * @param bottom bottom coordinate for the background
+     */
+    private fun drawStart(canvas: Canvas, start: Int, top: Int, end: Int, bottom: Int) {
+        if (start > end) {
+            drawableRight.setBounds(end, top, start, bottom)
+            drawableRight.draw(canvas)
+        } else {
+            drawableLeft.setBounds(start, top, end, bottom)
+            drawableLeft.draw(canvas)
+        }
+    }
+
+    /**
+     * Draw the last line of a multiline annotation. Handles LTR/RTL.
+     *
+     * @param canvas Canvas to draw onto
+     * @param start start coordinate for the background
+     * @param top top position for the background
+     * @param end end coordinate for the background
+     * @param bottom bottom coordinate for the background
+     */
+    private fun drawEnd(canvas: Canvas, start: Int, top: Int, end: Int, bottom: Int) {
+        if (start > end) {
+            drawableLeft.setBounds(end, top, start, bottom)
+            drawableLeft.draw(canvas)
+        } else {
+            drawableRight.setBounds(start, top, end, bottom)
+            drawableRight.draw(canvas)
+        }
+    }
+}
\ No newline at end of file
diff --git a/ui/text/RoundedBackground-Kotlin/lib/src/main/res/drawable/rounded_text_bg.xml b/ui/text/RoundedBackground-Kotlin/lib/src/main/res/drawable/rounded_text_bg.xml
new file mode 100644
index 0000000..0683b1b
--- /dev/null
+++ b/ui/text/RoundedBackground-Kotlin/lib/src/main/res/drawable/rounded_text_bg.xml
@@ -0,0 +1,21 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  ~ Copyright 2018 The Android Open Source Project
+  ~
+  ~ Licensed under the Apache License, Version 2.0 (the "License");
+  ~ you may not use this file except in compliance with the License.
+  ~ You may obtain a copy of the License at
+  ~
+  ~      http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing, software
+  ~ distributed under the License is distributed on an "AS IS" BASIS,
+  ~ WITHOUT 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="@color/roundedTextBg"/>
+    <stroke android:width="@dimen/roundedTextBorderWidth" android:color="@color/roundedTextBorder"/>
+    <corners android:radius="@dimen/roundedTextBorderRadius"/>
+</shape>
\ No newline at end of file
diff --git a/ui/text/RoundedBackground-Kotlin/lib/src/main/res/drawable/rounded_text_bg_left.xml b/ui/text/RoundedBackground-Kotlin/lib/src/main/res/drawable/rounded_text_bg_left.xml
new file mode 100644
index 0000000..5d02c93
--- /dev/null
+++ b/ui/text/RoundedBackground-Kotlin/lib/src/main/res/drawable/rounded_text_bg_left.xml
@@ -0,0 +1,22 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  ~ Copyright 2018 The Android Open Source Project
+  ~
+  ~ Licensed under the Apache License, Version 2.0 (the "License");
+  ~ you may not use this file except in compliance with the License.
+  ~ You may obtain a copy of the License at
+  ~
+  ~      http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing, software
+  ~ distributed under the License is distributed on an "AS IS" BASIS,
+  ~ WITHOUT 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="@color/roundedTextBg"/>
+    <stroke android:width="@dimen/roundedTextBorderWidth" android:color="@color/roundedTextBorder"/>
+    <corners android:topLeftRadius="@dimen/roundedTextBorderRadius"
+             android:bottomLeftRadius="@dimen/roundedTextBorderRadius"/>
+</shape>
\ No newline at end of file
diff --git a/ui/text/RoundedBackground-Kotlin/lib/src/main/res/drawable/rounded_text_bg_mid.xml b/ui/text/RoundedBackground-Kotlin/lib/src/main/res/drawable/rounded_text_bg_mid.xml
new file mode 100644
index 0000000..e4c2c57
--- /dev/null
+++ b/ui/text/RoundedBackground-Kotlin/lib/src/main/res/drawable/rounded_text_bg_mid.xml
@@ -0,0 +1,20 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  ~ Copyright 2018 The Android Open Source Project
+  ~
+  ~ Licensed under the Apache License, Version 2.0 (the "License");
+  ~ you may not use this file except in compliance with the License.
+  ~ You may obtain a copy of the License at
+  ~
+  ~      http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing, software
+  ~ distributed under the License is distributed on an "AS IS" BASIS,
+  ~ WITHOUT 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="@color/roundedTextBg"/>
+    <stroke android:width="@dimen/roundedTextBorderWidth" android:color="@color/roundedTextBorder"/>
+</shape>
\ No newline at end of file
diff --git a/ui/text/RoundedBackground-Kotlin/lib/src/main/res/drawable/rounded_text_bg_right.xml b/ui/text/RoundedBackground-Kotlin/lib/src/main/res/drawable/rounded_text_bg_right.xml
new file mode 100644
index 0000000..cf011c5
--- /dev/null
+++ b/ui/text/RoundedBackground-Kotlin/lib/src/main/res/drawable/rounded_text_bg_right.xml
@@ -0,0 +1,22 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  ~ Copyright 2018 The Android Open Source Project
+  ~
+  ~ Licensed under the Apache License, Version 2.0 (the "License");
+  ~ you may not use this file except in compliance with the License.
+  ~ You may obtain a copy of the License at
+  ~
+  ~      http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing, software
+  ~ distributed under the License is distributed on an "AS IS" BASIS,
+  ~ WITHOUT 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="@color/roundedTextBg"/>
+    <stroke android:width="@dimen/roundedTextBorderWidth" android:color="@color/roundedTextBorder"/>
+    <corners android:topRightRadius="@dimen/roundedTextBorderRadius"
+             android:bottomRightRadius="@dimen/roundedTextBorderRadius"/>
+</shape>
\ No newline at end of file
diff --git a/ui/text/RoundedBackground-Kotlin/lib/src/main/res/values/attrs.xml b/ui/text/RoundedBackground-Kotlin/lib/src/main/res/values/attrs.xml
new file mode 100644
index 0000000..737ae07
--- /dev/null
+++ b/ui/text/RoundedBackground-Kotlin/lib/src/main/res/values/attrs.xml
@@ -0,0 +1,26 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  ~ Copyright 2018 The Android Open Source Project
+  ~
+  ~ Licensed under the Apache License, Version 2.0 (the "License");
+  ~ you may not use this file except in compliance with the License.
+  ~ You may obtain a copy of the License at
+  ~
+  ~      http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing, software
+  ~ distributed under the License is distributed on an "AS IS" BASIS,
+  ~ WITHOUT 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>
+    <declare-styleable name="TextRoundedBgHelper">
+        <attr name="roundedTextHorizontalPadding" format="dimension"/>
+        <attr name="roundedTextVerticalPadding" format="dimension"/>
+        <attr name="roundedTextDrawable" format="reference"/>
+        <attr name="roundedTextDrawableLeft" format="reference"/>
+        <attr name="roundedTextDrawableMid" format="reference"/>
+        <attr name="roundedTextDrawableRight" format="reference"/>
+    </declare-styleable>
+</resources>
\ No newline at end of file
diff --git a/ui/text/RoundedBackground-Kotlin/lib/src/main/res/values/colors.xml b/ui/text/RoundedBackground-Kotlin/lib/src/main/res/values/colors.xml
new file mode 100644
index 0000000..c26ac6a
--- /dev/null
+++ b/ui/text/RoundedBackground-Kotlin/lib/src/main/res/values/colors.xml
@@ -0,0 +1,20 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  ~ Copyright 2018 The Android Open Source Project
+  ~
+  ~ Licensed under the Apache License, Version 2.0 (the "License");
+  ~ you may not use this file except in compliance with the License.
+  ~ You may obtain a copy of the License at
+  ~
+  ~      http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing, software
+  ~ distributed under the License is distributed on an "AS IS" BASIS,
+  ~ WITHOUT 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="roundedTextBg">#554fC3f7</color>
+    <color name="roundedTextBorder">#DD0277BD</color>
+</resources>
diff --git a/ui/text/RoundedBackground-Kotlin/lib/src/main/res/values/dimens.xml b/ui/text/RoundedBackground-Kotlin/lib/src/main/res/values/dimens.xml
new file mode 100644
index 0000000..dbc7f49
--- /dev/null
+++ b/ui/text/RoundedBackground-Kotlin/lib/src/main/res/values/dimens.xml
@@ -0,0 +1,20 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  ~ Copyright 2018 The Android Open Source Project
+  ~
+  ~ Licensed under the Apache License, Version 2.0 (the "License");
+  ~ you may not use this file except in compliance with the License.
+  ~ You may obtain a copy of the License at
+  ~
+  ~      http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing, software
+  ~ distributed under the License is distributed on an "AS IS" BASIS,
+  ~ WITHOUT 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="roundedTextBorderRadius">4dp</dimen>
+    <dimen name="roundedTextBorderWidth">1dp</dimen>
+</resources>
\ No newline at end of file
diff --git a/ui/text/RoundedBackground-Kotlin/lib/src/main/res/values/styles.xml b/ui/text/RoundedBackground-Kotlin/lib/src/main/res/values/styles.xml
new file mode 100644
index 0000000..9bffef3
--- /dev/null
+++ b/ui/text/RoundedBackground-Kotlin/lib/src/main/res/values/styles.xml
@@ -0,0 +1,28 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  ~ Copyright 2018 The Android Open Source Project
+  ~
+  ~ Licensed under the Apache License, Version 2.0 (the "License");
+  ~ you may not use this file except in compliance with the License.
+  ~ You may obtain a copy of the License at
+  ~
+  ~      http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing, software
+  ~ distributed under the License is distributed on an "AS IS" BASIS,
+  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  ~ See the License for the specific language governing permissions and
+  ~ limitations under the License.
+  -->
+<resources>
+
+    <style name="RoundedBgTextView" parent="@android:style/Widget.TextView">
+        <item name="roundedTextHorizontalPadding">2dp</item>
+        <item name="roundedTextVerticalPadding">2dp</item>
+        <item name="roundedTextDrawable">@drawable/rounded_text_bg</item>
+        <item name="roundedTextDrawableLeft">@drawable/rounded_text_bg_left</item>
+        <item name="roundedTextDrawableMid">@drawable/rounded_text_bg_mid</item>
+        <item name="roundedTextDrawableRight">@drawable/rounded_text_bg_right</item>
+    </style>
+
+</resources>
diff --git a/ui/text/RoundedBackground-Kotlin/settings.gradle b/ui/text/RoundedBackground-Kotlin/settings.gradle
new file mode 100644
index 0000000..3cbe249
--- /dev/null
+++ b/ui/text/RoundedBackground-Kotlin/settings.gradle
@@ -0,0 +1 @@
+include ':app', ':lib'
diff --git a/ui/text/screenshots/lines.png b/ui/text/screenshots/lines.png
new file mode 100644
index 0000000..dbc86d1
--- /dev/null
+++ b/ui/text/screenshots/lines.png
Binary files differ
diff --git a/ui/text/screenshots/multi.png b/ui/text/screenshots/multi.png
new file mode 100644
index 0000000..e37d1e1
--- /dev/null
+++ b/ui/text/screenshots/multi.png
Binary files differ
diff --git a/ui/text/screenshots/rounded_bg.png b/ui/text/screenshots/rounded_bg.png
new file mode 100644
index 0000000..1e9acdd
--- /dev/null
+++ b/ui/text/screenshots/rounded_bg.png
Binary files differ
diff --git a/ui/text/screenshots/rtl.png b/ui/text/screenshots/rtl.png
new file mode 100644
index 0000000..6d8aba3
--- /dev/null
+++ b/ui/text/screenshots/rtl.png
Binary files differ
diff --git a/ui/text/screenshots/single.png b/ui/text/screenshots/single.png
new file mode 100644
index 0000000..b264682
--- /dev/null
+++ b/ui/text/screenshots/single.png
Binary files differ