Merge "First version of SliceView (hidden for now)"
diff --git a/core/java/android/content/ContentProvider.java b/core/java/android/content/ContentProvider.java
index cdeaea3..5b2bf45 100644
--- a/core/java/android/content/ContentProvider.java
+++ b/core/java/android/content/ContentProvider.java
@@ -2099,7 +2099,8 @@
     public static Uri maybeAddUserId(Uri uri, int userId) {
         if (uri == null) return null;
         if (userId != UserHandle.USER_CURRENT
-                && ContentResolver.SCHEME_CONTENT.equals(uri.getScheme())) {
+                && (ContentResolver.SCHEME_CONTENT.equals(uri.getScheme())
+                        || ContentResolver.SCHEME_SLICE.equals(uri.getScheme()))) {
             if (!uriHasUserId(uri)) {
                 //We don't add the user Id if there's already one
                 Uri.Builder builder = uri.buildUpon();
diff --git a/core/java/android/slice/Slice.java b/core/java/android/slice/Slice.java
index bb810e6..5768654 100644
--- a/core/java/android/slice/Slice.java
+++ b/core/java/android/slice/Slice.java
@@ -35,6 +35,8 @@
 import android.os.Parcelable;
 import android.widget.RemoteViews;
 
+import com.android.internal.util.ArrayUtils;
+
 import java.util.ArrayList;
 import java.util.Arrays;
 
@@ -136,7 +138,7 @@
     }
 
     /**
-     * @return The Uri that this slice represents.
+     * @return The Uri that this Slice represents.
      */
     public Uri getUri() {
         return mUri;
@@ -191,6 +193,13 @@
     }
 
     /**
+     * @hide
+     */
+    public boolean hasHint(@SliceHint String hint) {
+        return ArrayUtils.contains(mHints, hint);
+    }
+
+    /**
      * A Builder used to construct {@link Slice}s
      */
     public static class Builder {
@@ -308,4 +317,31 @@
             return new Slice[size];
         }
     };
+
+    /**
+     * @hide
+     * @return A string representation of this slice.
+     */
+    public String getString() {
+        return getString("");
+    }
+
+    private String getString(String indent) {
+        StringBuilder sb = new StringBuilder();
+        for (int i = 0; i < mItems.length; i++) {
+            sb.append(indent);
+            if (mItems[i].getType() == TYPE_SLICE) {
+                sb.append("slice:\n");
+                sb.append(mItems[i].getSlice().getString(indent + "   "));
+            } else if (mItems[i].getType() == TYPE_TEXT) {
+                sb.append("text: ");
+                sb.append(mItems[i].getText());
+                sb.append("\n");
+            } else {
+                sb.append(SliceItem.typeToString(mItems[i].getType()));
+                sb.append("\n");
+            }
+        }
+        return sb.toString();
+    }
 }
diff --git a/core/java/android/slice/SliceItem.java b/core/java/android/slice/SliceItem.java
index 16f7dc6..2827ab9 100644
--- a/core/java/android/slice/SliceItem.java
+++ b/core/java/android/slice/SliceItem.java
@@ -132,6 +132,13 @@
         mHints = ArrayUtils.appendElement(String.class, mHints, hint);
     }
 
+    /**
+     * @hide
+     */
+    public void removeHint(String hint) {
+        ArrayUtils.removeElement(String.class, mHints, hint);
+    }
+
     public @SliceType int getType() {
         return mType;
     }
@@ -230,7 +237,7 @@
     public boolean hasHints(@SliceHint String[] hints) {
         if (hints == null) return true;
         for (String hint : hints) {
-            if (!ArrayUtils.contains(mHints, hint)) {
+            if (!TextUtils.isEmpty(hint) && !ArrayUtils.contains(mHints, hint)) {
                 return false;
             }
         }
@@ -241,7 +248,7 @@
      * @hide
      */
     public boolean hasAnyHints(@SliceHint String[] hints) {
-        if (hints == null) return true;
+        if (hints == null) return false;
         for (String hint : hints) {
             if (ArrayUtils.contains(mHints, hint)) {
                 return true;
@@ -309,4 +316,29 @@
             return new SliceItem[size];
         }
     };
+
+    /**
+     * @hide
+     */
+    public static String typeToString(int type) {
+        switch (type) {
+            case TYPE_SLICE:
+                return "Slice";
+            case TYPE_TEXT:
+                return "Text";
+            case TYPE_IMAGE:
+                return "Image";
+            case TYPE_ACTION:
+                return "Action";
+            case TYPE_REMOTE_VIEW:
+                return "RemoteView";
+            case TYPE_COLOR:
+                return "Color";
+            case TYPE_TIMESTAMP:
+                return "Timestamp";
+            case TYPE_REMOTE_INPUT:
+                return "RemoteInput";
+        }
+        return "Unrecognized type: " + type;
+    }
 }
diff --git a/core/java/android/slice/SliceQuery.java b/core/java/android/slice/SliceQuery.java
index edac0cc..d99b26a 100644
--- a/core/java/android/slice/SliceQuery.java
+++ b/core/java/android/slice/SliceQuery.java
@@ -61,6 +61,13 @@
     /**
      * @hide
      */
+    public static List<SliceItem> findAll(SliceItem s, int type) {
+        return findAll(s, type, (String[]) null, null);
+    }
+
+    /**
+     * @hide
+     */
     public static List<SliceItem> findAll(SliceItem s, int type, String hints, String nonHints) {
         return findAll(s, type, new String[]{ hints }, new String[]{ nonHints });
     }
@@ -85,6 +92,13 @@
     /**
      * @hide
      */
+    public static SliceItem find(Slice s, int type) {
+        return find(s, type, (String[]) null, null);
+    }
+
+    /**
+     * @hide
+     */
     public static SliceItem find(SliceItem s, int type) {
         return find(s, type, (String[]) null, null);
     }
diff --git a/core/java/android/slice/views/ActionRow.java b/core/java/android/slice/views/ActionRow.java
new file mode 100644
index 0000000..93e9c03
--- /dev/null
+++ b/core/java/android/slice/views/ActionRow.java
@@ -0,0 +1,201 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.slice.views;
+
+import android.app.PendingIntent;
+import android.app.PendingIntent.CanceledException;
+import android.app.RemoteInput;
+import android.content.Context;
+import android.content.res.ColorStateList;
+import android.graphics.Color;
+import android.graphics.drawable.Icon;
+import android.os.AsyncTask;
+import android.slice.Slice;
+import android.slice.SliceItem;
+import android.slice.SliceQuery;
+import android.util.TypedValue;
+import android.view.View;
+import android.view.ViewParent;
+import android.widget.FrameLayout;
+import android.widget.ImageView;
+import android.widget.ImageView.ScaleType;
+import android.widget.LinearLayout;
+import android.widget.TextView;
+
+/**
+ * @hide
+ */
+public class ActionRow extends FrameLayout {
+
+    private static final int MAX_ACTIONS = 5;
+    private final int mSize;
+    private final int mIconPadding;
+    private final LinearLayout mActionsGroup;
+    private final boolean mFullActions;
+    private int mColor = Color.BLACK;
+
+    public ActionRow(Context context, boolean fullActions) {
+        super(context);
+        mFullActions = fullActions;
+        mSize = (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 48,
+                context.getResources().getDisplayMetrics());
+        mIconPadding = (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 12,
+                context.getResources().getDisplayMetrics());
+        mActionsGroup = new LinearLayout(context);
+        mActionsGroup.setOrientation(LinearLayout.HORIZONTAL);
+        mActionsGroup.setLayoutParams(
+                new LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.WRAP_CONTENT));
+        addView(mActionsGroup);
+    }
+
+    private void setColor(int color) {
+        mColor = color;
+        for (int i = 0; i < mActionsGroup.getChildCount(); i++) {
+            View view = mActionsGroup.getChildAt(i);
+            SliceItem item = (SliceItem) view.getTag();
+            boolean tint = !item.hasHint(Slice.HINT_NO_TINT);
+            if (tint) {
+                ((ImageView) view).setImageTintList(ColorStateList.valueOf(mColor));
+            }
+        }
+    }
+
+    private ImageView addAction(Icon icon, boolean allowTint, SliceItem image) {
+        ImageView imageView = new ImageView(getContext());
+        imageView.setPadding(mIconPadding, mIconPadding, mIconPadding, mIconPadding);
+        imageView.setScaleType(ScaleType.FIT_CENTER);
+        imageView.setImageIcon(icon);
+        if (allowTint) {
+            imageView.setImageTintList(ColorStateList.valueOf(mColor));
+        }
+        imageView.setBackground(SliceViewUtil.getDrawable(getContext(),
+                android.R.attr.selectableItemBackground));
+        imageView.setTag(image);
+        addAction(imageView);
+        return imageView;
+    }
+
+    /**
+     * Set the actions and color for this action row.
+     */
+    public void setActions(SliceItem actionRow, SliceItem defColor) {
+        removeAllViews();
+        mActionsGroup.removeAllViews();
+        addView(mActionsGroup);
+
+        SliceItem color = SliceQuery.find(actionRow, SliceItem.TYPE_COLOR);
+        if (color == null) {
+            color = defColor;
+        }
+        if (color != null) {
+            setColor(color.getColor());
+        }
+        SliceQuery.findAll(actionRow, SliceItem.TYPE_ACTION).forEach(action -> {
+            if (mActionsGroup.getChildCount() >= MAX_ACTIONS) {
+                return;
+            }
+            SliceItem image = SliceQuery.find(action, SliceItem.TYPE_IMAGE);
+            if (image == null) {
+                return;
+            }
+            boolean tint = !image.hasHint(Slice.HINT_NO_TINT);
+            SliceItem input = SliceQuery.find(action, SliceItem.TYPE_REMOTE_INPUT);
+            if (input != null && input.getRemoteInput().getAllowFreeFormInput()) {
+                addAction(image.getIcon(), tint, image).setOnClickListener(
+                        v -> handleRemoteInputClick(v, action.getAction(), input.getRemoteInput()));
+                createRemoteInputView(mColor, getContext());
+            } else {
+                addAction(image.getIcon(), tint, image).setOnClickListener(v -> AsyncTask.execute(
+                        () -> {
+                            try {
+                                action.getAction().send();
+                            } catch (CanceledException e) {
+                                e.printStackTrace();
+                            }
+                        }));
+            }
+        });
+        setVisibility(getChildCount() != 0 ? View.VISIBLE : View.GONE);
+    }
+
+    private void addAction(View child) {
+        mActionsGroup.addView(child, new LinearLayout.LayoutParams(mSize, mSize, 1));
+    }
+
+    private void createRemoteInputView(int color, Context context) {
+        View riv = RemoteInputView.inflate(context, this);
+        riv.setVisibility(View.INVISIBLE);
+        addView(riv, new LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT));
+        riv.setBackgroundColor(color);
+    }
+
+    private boolean handleRemoteInputClick(View view, PendingIntent pendingIntent,
+            RemoteInput input) {
+        if (input == null) {
+            return false;
+        }
+
+        ViewParent p = view.getParent().getParent();
+        RemoteInputView riv = null;
+        while (p != null) {
+            if (p instanceof View) {
+                View pv = (View) p;
+                riv = findRemoteInputView(pv);
+                if (riv != null) {
+                    break;
+                }
+            }
+            p = p.getParent();
+        }
+        if (riv == null) {
+            return false;
+        }
+
+        int width = view.getWidth();
+        if (view instanceof TextView) {
+            // Center the reveal on the text which might be off-center from the TextView
+            TextView tv = (TextView) view;
+            if (tv.getLayout() != null) {
+                int innerWidth = (int) tv.getLayout().getLineWidth(0);
+                innerWidth += tv.getCompoundPaddingLeft() + tv.getCompoundPaddingRight();
+                width = Math.min(width, innerWidth);
+            }
+        }
+        int cx = view.getLeft() + width / 2;
+        int cy = view.getTop() + view.getHeight() / 2;
+        int w = riv.getWidth();
+        int h = riv.getHeight();
+        int r = Math.max(
+                Math.max(cx + cy, cx + (h - cy)),
+                Math.max((w - cx) + cy, (w - cx) + (h - cy)));
+
+        riv.setRevealParameters(cx, cy, r);
+        riv.setPendingIntent(pendingIntent);
+        riv.setRemoteInput(new RemoteInput[] {
+                input
+        }, input);
+        riv.focusAnimated();
+        return true;
+    }
+
+    private RemoteInputView findRemoteInputView(View v) {
+        if (v == null) {
+            return null;
+        }
+        return (RemoteInputView) v.findViewWithTag(RemoteInputView.VIEW_TAG);
+    }
+}
diff --git a/core/java/android/slice/views/GridView.java b/core/java/android/slice/views/GridView.java
new file mode 100644
index 0000000..18a90f7
--- /dev/null
+++ b/core/java/android/slice/views/GridView.java
@@ -0,0 +1,186 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.slice.views;
+
+import static android.view.ViewGroup.LayoutParams.MATCH_PARENT;
+import static android.view.ViewGroup.LayoutParams.WRAP_CONTENT;
+
+import android.content.Context;
+import android.graphics.Color;
+import android.slice.Slice;
+import android.slice.SliceItem;
+import android.slice.views.LargeSliceAdapter.SliceListView;
+import android.util.AttributeSet;
+import android.util.TypedValue;
+import android.view.Gravity;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.widget.FrameLayout;
+import android.widget.ImageView;
+import android.widget.ImageView.ScaleType;
+import android.widget.LinearLayout;
+import android.widget.TextView;
+
+import com.android.internal.R;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+
+/**
+ * @hide
+ */
+public class GridView extends LinearLayout implements SliceListView {
+
+    private static final String TAG = "GridView";
+
+    private static final int MAX_IMAGES = 3;
+    private static final int MAX_ALL = 5;
+    private boolean mIsAllImages;
+
+    public GridView(Context context, AttributeSet attrs) {
+        super(context, attrs);
+    }
+
+    @Override
+    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
+        if (mIsAllImages) {
+            int width = MeasureSpec.getSize(widthMeasureSpec);
+            int height = width / getChildCount();
+            heightMeasureSpec = MeasureSpec.makeMeasureSpec(MeasureSpec.EXACTLY,
+                    height);
+            getLayoutParams().height = height;
+            for (int i = 0; i < getChildCount(); i++) {
+                getChildAt(i).getLayoutParams().height = height;
+            }
+        }
+        super.onMeasure(widthMeasureSpec, heightMeasureSpec);
+    }
+
+    @Override
+    public void setSliceItem(SliceItem slice) {
+        mIsAllImages = true;
+        removeAllViews();
+        int total = 1;
+        if (slice.getType() == SliceItem.TYPE_SLICE) {
+            SliceItem[] items = slice.getSlice().getItems();
+            total = items.length;
+            for (int i = 0; i < total; i++) {
+                SliceItem item = items[i];
+                if (isFull()) {
+                    continue;
+                }
+                if (!addItem(item)) {
+                    mIsAllImages = false;
+                }
+            }
+        } else {
+            if (!isFull()) {
+                if (!addItem(slice)) {
+                    mIsAllImages = false;
+                }
+            }
+        }
+        if (total > getChildCount() && mIsAllImages) {
+            addExtraCount(total - getChildCount());
+        }
+    }
+
+    private void addExtraCount(int numExtra) {
+        View last = getChildAt(getChildCount() - 1);
+        FrameLayout frame = new FrameLayout(getContext());
+        frame.setLayoutParams(last.getLayoutParams());
+
+        removeView(last);
+        frame.addView(last, new LayoutParams(MATCH_PARENT, MATCH_PARENT));
+
+        TextView v = new TextView(getContext());
+        v.setTextColor(Color.WHITE);
+        v.setBackgroundColor(0x4d000000);
+        v.setText(getResources().getString(R.string.slice_more_content, numExtra));
+        v.setTextSize(TypedValue.COMPLEX_UNIT_DIP, 18);
+        v.setGravity(Gravity.CENTER);
+        frame.addView(v, new LayoutParams(MATCH_PARENT, MATCH_PARENT));
+
+        addView(frame);
+    }
+
+    private boolean isFull() {
+        return getChildCount() >= (mIsAllImages ? MAX_IMAGES : MAX_ALL);
+    }
+
+    /**
+     * Returns true if this item is just an image.
+     */
+    private boolean addItem(SliceItem item) {
+        if (item.getType() == SliceItem.TYPE_IMAGE) {
+            ImageView v = new ImageView(getContext());
+            v.setImageIcon(item.getIcon());
+            v.setScaleType(ScaleType.CENTER_CROP);
+            addView(v, new LayoutParams(0, MATCH_PARENT, 1));
+            return true;
+        } else {
+            LinearLayout v = new LinearLayout(getContext());
+            int s = (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP,
+                    12, getContext().getResources().getDisplayMetrics());
+            v.setPadding(0, s, 0, 0);
+            v.setOrientation(LinearLayout.VERTICAL);
+            v.setGravity(Gravity.CENTER_HORIZONTAL);
+            // TODO: Unify sporadic inflates that happen throughout the code.
+            ArrayList<SliceItem> items = new ArrayList<>();
+            if (item.getType() == SliceItem.TYPE_SLICE) {
+                items.addAll(Arrays.asList(item.getSlice().getItems()));
+            }
+            items.forEach(i -> {
+                Context context = getContext();
+                switch (i.getType()) {
+                    case SliceItem.TYPE_TEXT:
+                        boolean title = false;
+                        if ((item.hasAnyHints(new String[] {
+                                Slice.HINT_LARGE, Slice.HINT_TITLE
+                        }))) {
+                            title = true;
+                        }
+                        TextView tv = (TextView) LayoutInflater.from(context).inflate(
+                                title ? R.layout.slice_title : R.layout.slice_secondary_text, null);
+                        tv.setText(i.getText());
+                        v.addView(tv);
+                        break;
+                    case SliceItem.TYPE_IMAGE:
+                        ImageView iv = new ImageView(context);
+                        iv.setImageIcon(i.getIcon());
+                        if (item.hasHint(Slice.HINT_LARGE)) {
+                            iv.setLayoutParams(new LayoutParams(WRAP_CONTENT, WRAP_CONTENT));
+                        } else {
+                            int size = (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP,
+                                    48, context.getResources().getDisplayMetrics());
+                            iv.setLayoutParams(new LayoutParams(size, size));
+                        }
+                        v.addView(iv);
+                        break;
+                    case SliceItem.TYPE_REMOTE_VIEW:
+                        v.addView(i.getRemoteView().apply(context, v));
+                        break;
+                    case SliceItem.TYPE_COLOR:
+                        // TODO: Support color to tint stuff here.
+                        break;
+                }
+            });
+            addView(v, new LayoutParams(0, WRAP_CONTENT, 1));
+            return false;
+        }
+    }
+}
diff --git a/core/java/android/slice/views/LargeSliceAdapter.java b/core/java/android/slice/views/LargeSliceAdapter.java
new file mode 100644
index 0000000..e77a1b2
--- /dev/null
+++ b/core/java/android/slice/views/LargeSliceAdapter.java
@@ -0,0 +1,224 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.slice.views;
+
+import android.content.Context;
+import android.slice.Slice;
+import android.slice.SliceItem;
+import android.slice.SliceQuery;
+import android.slice.views.LargeSliceAdapter.SliceViewHolder;
+import android.util.ArrayMap;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+import android.view.ViewGroup.LayoutParams;
+import android.widget.FrameLayout;
+
+import com.android.internal.R;
+import com.android.internal.widget.RecyclerView;
+import com.android.internal.widget.RecyclerView.ViewHolder;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.stream.Collectors;
+
+/**
+ * @hide
+ */
+public class LargeSliceAdapter extends RecyclerView.Adapter<SliceViewHolder> {
+
+    public static final int TYPE_DEFAULT       = 1;
+    public static final int TYPE_HEADER        = 2;
+    public static final int TYPE_GRID          = 3;
+    public static final int TYPE_MESSAGE       = 4;
+    public static final int TYPE_MESSAGE_LOCAL = 5;
+    public static final int TYPE_REMOTE_VIEWS  = 6;
+
+    private final IdGenerator mIdGen = new IdGenerator();
+    private final Context mContext;
+    private List<SliceWrapper> mSlices = new ArrayList<>();
+    private SliceItem mColor;
+
+    public LargeSliceAdapter(Context context) {
+        mContext = context;
+        setHasStableIds(true);
+    }
+
+    /**
+     * Set the {@link SliceItem}'s to be displayed in the adapter and the accent color.
+     */
+    public void setSliceItems(List<SliceItem> slices, SliceItem color) {
+        mColor = color;
+        mIdGen.resetUsage();
+        mSlices = slices.stream().map(s -> new SliceWrapper(s, mIdGen))
+                .collect(Collectors.toList());
+        notifyDataSetChanged();
+    }
+
+    @Override
+    public SliceViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
+        View v = inflateforType(viewType);
+        v.setLayoutParams(new LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.WRAP_CONTENT));
+        return new SliceViewHolder(v);
+    }
+
+    @Override
+    public int getItemViewType(int position) {
+        return mSlices.get(position).mType;
+    }
+
+    @Override
+    public long getItemId(int position) {
+        return mSlices.get(position).mId;
+    }
+
+    @Override
+    public int getItemCount() {
+        return mSlices.size();
+    }
+
+    @Override
+    public void onBindViewHolder(SliceViewHolder holder, int position) {
+        SliceWrapper slice = mSlices.get(position);
+        if (holder.mSliceView != null) {
+            holder.mSliceView.setColor(mColor);
+            holder.mSliceView.setSliceItem(slice.mItem);
+        } else if (slice.mType == TYPE_REMOTE_VIEWS) {
+            FrameLayout frame = (FrameLayout) holder.itemView;
+            frame.removeAllViews();
+            frame.addView(slice.mItem.getRemoteView().apply(mContext, frame));
+        }
+    }
+
+    private View inflateforType(int viewType) {
+        switch (viewType) {
+            case TYPE_REMOTE_VIEWS:
+                return new FrameLayout(mContext);
+            case TYPE_GRID:
+                return LayoutInflater.from(mContext).inflate(R.layout.slice_grid, null);
+            case TYPE_MESSAGE:
+                return LayoutInflater.from(mContext).inflate(R.layout.slice_message, null);
+            case TYPE_MESSAGE_LOCAL:
+                return LayoutInflater.from(mContext).inflate(R.layout.slice_message_local, null);
+        }
+        return new SmallTemplateView(mContext);
+    }
+
+    protected static class SliceWrapper {
+        private final SliceItem mItem;
+        private final int mType;
+        private final long mId;
+
+        public SliceWrapper(SliceItem item, IdGenerator idGen) {
+            mItem = item;
+            mType = getType(item);
+            mId = idGen.getId(item);
+        }
+
+        public static int getType(SliceItem item) {
+            if (item.getType() == SliceItem.TYPE_REMOTE_VIEW) {
+                return TYPE_REMOTE_VIEWS;
+            }
+            if (item.hasHint(Slice.HINT_MESSAGE)) {
+                // TODO: Better way to determine me or not? Something more like Messaging style.
+                if (SliceQuery.find(item, -1, Slice.HINT_SOURCE, null) != null) {
+                    return TYPE_MESSAGE;
+                } else {
+                    return TYPE_MESSAGE_LOCAL;
+                }
+            }
+            if (item.hasHint(Slice.HINT_HORIZONTAL)) {
+                return TYPE_GRID;
+            }
+            return TYPE_DEFAULT;
+        }
+    }
+
+    /**
+     * A {@link ViewHolder} for presenting slices in {@link LargeSliceAdapter}.
+     */
+    public static class SliceViewHolder extends ViewHolder {
+        public final SliceListView mSliceView;
+
+        public SliceViewHolder(View itemView) {
+            super(itemView);
+            mSliceView = itemView instanceof SliceListView ? (SliceListView) itemView : null;
+        }
+    }
+
+    /**
+     * View slices being displayed in {@link LargeSliceAdapter}.
+     */
+    public interface SliceListView {
+        /**
+         * Set the slice item for this view.
+         */
+        void setSliceItem(SliceItem slice);
+
+        /**
+         * Set the color for the items in this view.
+         */
+        default void setColor(SliceItem color) {
+
+        }
+    }
+
+    private static class IdGenerator {
+        private long mNextLong = 0;
+        private final ArrayMap<String, Long> mCurrentIds = new ArrayMap<>();
+        private final ArrayMap<String, Integer> mUsedIds = new ArrayMap<>();
+
+        public long getId(SliceItem item) {
+            String str = genString(item);
+            if (!mCurrentIds.containsKey(str)) {
+                mCurrentIds.put(str, mNextLong++);
+            }
+            long id = mCurrentIds.get(str);
+            int index = mUsedIds.getOrDefault(str, 0);
+            mUsedIds.put(str, index + 1);
+            return id + index * 10000;
+        }
+
+        private String genString(SliceItem item) {
+            StringBuilder builder = new StringBuilder();
+            SliceQuery.stream(item).forEach(i -> {
+                builder.append(i.getType());
+                i.removeHint(Slice.HINT_SELECTED);
+                builder.append(i.getHints());
+                switch (i.getType()) {
+                    case SliceItem.TYPE_REMOTE_VIEW:
+                        builder.append(i.getRemoteView());
+                        break;
+                    case SliceItem.TYPE_IMAGE:
+                        builder.append(i.getIcon());
+                        break;
+                    case SliceItem.TYPE_TEXT:
+                        builder.append(i.getText());
+                        break;
+                    case SliceItem.TYPE_COLOR:
+                        builder.append(i.getColor());
+                        break;
+                }
+            });
+            return builder.toString();
+        }
+
+        public void resetUsage() {
+            mUsedIds.clear();
+        }
+    }
+}
diff --git a/core/java/android/slice/views/LargeTemplateView.java b/core/java/android/slice/views/LargeTemplateView.java
new file mode 100644
index 0000000..d53e8fc
--- /dev/null
+++ b/core/java/android/slice/views/LargeTemplateView.java
@@ -0,0 +1,116 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.slice.views;
+
+import static android.view.ViewGroup.LayoutParams.WRAP_CONTENT;
+
+import android.content.Context;
+import android.slice.Slice;
+import android.slice.SliceItem;
+import android.slice.SliceQuery;
+import android.slice.views.SliceView.SliceModeView;
+import android.util.TypedValue;
+
+import com.android.internal.widget.LinearLayoutManager;
+import com.android.internal.widget.RecyclerView;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+
+/**
+ * @hide
+ */
+public class LargeTemplateView extends SliceModeView {
+    private final LargeSliceAdapter mAdapter;
+    private final RecyclerView mRecyclerView;
+    private final int mDefaultHeight;
+    private final int mMaxHeight;
+    private Slice mSlice;
+
+    public LargeTemplateView(Context context) {
+        super(context);
+
+        mRecyclerView = new RecyclerView(getContext());
+        mRecyclerView.setLayoutManager(new LinearLayoutManager(getContext()));
+        mAdapter = new LargeSliceAdapter(context);
+        mRecyclerView.setAdapter(mAdapter);
+        addView(mRecyclerView);
+        int width = (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 300,
+                getResources().getDisplayMetrics());
+        setLayoutParams(new LayoutParams(width, WRAP_CONTENT));
+        mDefaultHeight = (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 200,
+                getResources().getDisplayMetrics());
+        mMaxHeight = (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 200,
+                getResources().getDisplayMetrics());
+    }
+
+    @Override
+    public String getMode() {
+        return SliceView.MODE_LARGE;
+    }
+
+    @Override
+    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
+        mRecyclerView.getLayoutParams().height = WRAP_CONTENT;
+        super.onMeasure(widthMeasureSpec, heightMeasureSpec);
+        if (mRecyclerView.getMeasuredHeight() > mMaxHeight
+                || mSlice.hasHint(Slice.HINT_PARTIAL)) {
+            mRecyclerView.getLayoutParams().height = mDefaultHeight;
+        } else {
+            mRecyclerView.getLayoutParams().height = mRecyclerView.getMeasuredHeight();
+        }
+        super.onMeasure(widthMeasureSpec, heightMeasureSpec);
+    }
+
+    @Override
+    public void setSlice(Slice slice) {
+        SliceItem color = SliceQuery.find(slice, SliceItem.TYPE_COLOR);
+        mSlice = slice;
+        List<SliceItem> items = new ArrayList<>();
+        boolean[] hasHeader = new boolean[1];
+        if (slice.hasHint(Slice.HINT_LIST)) {
+            addList(slice, items);
+        } else {
+            Arrays.asList(slice.getItems()).forEach(item -> {
+                if (item.hasHint(Slice.HINT_ACTIONS)) {
+                    return;
+                } else if (item.getType() == SliceItem.TYPE_COLOR) {
+                    return;
+                } else if (item.getType() == SliceItem.TYPE_SLICE
+                        && item.hasHint(Slice.HINT_LIST)) {
+                    addList(item.getSlice(), items);
+                } else if (item.hasHint(Slice.HINT_LIST_ITEM)) {
+                    items.add(item);
+                } else if (!hasHeader[0]) {
+                    hasHeader[0] = true;
+                    items.add(0, item);
+                } else {
+                    item.addHint(Slice.HINT_LIST_ITEM);
+                    items.add(item);
+                }
+            });
+        }
+        mAdapter.setSliceItems(items, color);
+    }
+
+    private void addList(Slice slice, List<SliceItem> items) {
+        List<SliceItem> sliceItems = Arrays.asList(slice.getItems());
+        sliceItems.forEach(i -> i.addHint(Slice.HINT_LIST_ITEM));
+        items.addAll(sliceItems);
+    }
+}
diff --git a/core/java/android/slice/views/MessageView.java b/core/java/android/slice/views/MessageView.java
new file mode 100644
index 0000000..7b03e0b
--- /dev/null
+++ b/core/java/android/slice/views/MessageView.java
@@ -0,0 +1,77 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.slice.views;
+
+import android.content.Context;
+import android.graphics.Bitmap;
+import android.graphics.Canvas;
+import android.graphics.drawable.Drawable;
+import android.slice.Slice;
+import android.slice.SliceItem;
+import android.slice.SliceQuery;
+import android.slice.views.LargeSliceAdapter.SliceListView;
+import android.text.SpannableStringBuilder;
+import android.util.AttributeSet;
+import android.util.TypedValue;
+import android.widget.ImageView;
+import android.widget.LinearLayout;
+import android.widget.TextView;
+
+/**
+ * @hide
+ */
+public class MessageView extends LinearLayout implements SliceListView {
+
+    private TextView mDetails;
+    private ImageView mIcon;
+
+    public MessageView(Context context, AttributeSet attrs) {
+        super(context, attrs);
+    }
+
+    @Override
+    protected void onFinishInflate() {
+        super.onFinishInflate();
+        mDetails = findViewById(android.R.id.summary);
+        mIcon = findViewById(android.R.id.icon);
+    }
+
+    @Override
+    public void setSliceItem(SliceItem slice) {
+        SliceItem source = SliceQuery.find(slice, SliceItem.TYPE_IMAGE, Slice.HINT_SOURCE, null);
+        if (source != null) {
+            final int iconSize = (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP,
+                    24, getContext().getResources().getDisplayMetrics());
+            // TODO try and turn this into a drawable
+            Bitmap iconBm = Bitmap.createBitmap(iconSize, iconSize, Bitmap.Config.ARGB_8888);
+            Canvas iconCanvas = new Canvas(iconBm);
+            Drawable d = source.getIcon().loadDrawable(getContext());
+            d.setBounds(0, 0, iconSize, iconSize);
+            d.draw(iconCanvas);
+            mIcon.setImageBitmap(SliceViewUtil.getCircularBitmap(iconBm));
+        }
+        SpannableStringBuilder builder = new SpannableStringBuilder();
+        SliceQuery.findAll(slice, SliceItem.TYPE_TEXT).forEach(text -> {
+            if (builder.length() != 0) {
+                builder.append('\n');
+            }
+            builder.append(text.getText());
+        });
+        mDetails.setText(builder.toString());
+    }
+
+}
diff --git a/core/java/android/slice/views/RemoteInputView.java b/core/java/android/slice/views/RemoteInputView.java
new file mode 100644
index 0000000..a29bb5c
--- /dev/null
+++ b/core/java/android/slice/views/RemoteInputView.java
@@ -0,0 +1,445 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.slice.views;
+
+import android.animation.Animator;
+import android.app.Notification;
+import android.app.PendingIntent;
+import android.app.RemoteInput;
+import android.content.Context;
+import android.content.Intent;
+import android.content.pm.ShortcutManager;
+import android.graphics.Rect;
+import android.graphics.drawable.Drawable;
+import android.os.Bundle;
+import android.text.Editable;
+import android.text.TextWatcher;
+import android.util.AttributeSet;
+import android.util.Log;
+import android.view.KeyEvent;
+import android.view.LayoutInflater;
+import android.view.MotionEvent;
+import android.view.View;
+import android.view.ViewAnimationUtils;
+import android.view.ViewGroup;
+import android.view.accessibility.AccessibilityEvent;
+import android.view.inputmethod.CompletionInfo;
+import android.view.inputmethod.EditorInfo;
+import android.view.inputmethod.InputConnection;
+import android.view.inputmethod.InputMethodManager;
+import android.widget.EditText;
+import android.widget.ImageButton;
+import android.widget.LinearLayout;
+import android.widget.ProgressBar;
+import android.widget.TextView;
+import android.widget.Toast;
+
+import com.android.internal.R;
+
+/**
+ * Host for the remote input.
+ *
+ * @hide
+ */
+// TODO this should be unified with SystemUI RemoteInputView (b/67527720)
+public class RemoteInputView extends LinearLayout implements View.OnClickListener, TextWatcher {
+
+    private static final String TAG = "RemoteInput";
+
+    /**
+     * A marker object that let's us easily find views of this class.
+     */
+    public static final Object VIEW_TAG = new Object();
+
+    private RemoteEditText mEditText;
+    private ImageButton mSendButton;
+    private ProgressBar mProgressBar;
+    private PendingIntent mPendingIntent;
+    private RemoteInput[] mRemoteInputs;
+    private RemoteInput mRemoteInput;
+
+    private int mRevealCx;
+    private int mRevealCy;
+    private int mRevealR;
+    private boolean mResetting;
+
+    public RemoteInputView(Context context, AttributeSet attrs) {
+        super(context, attrs);
+    }
+
+    @Override
+    protected void onFinishInflate() {
+        super.onFinishInflate();
+
+        mProgressBar = findViewById(R.id.remote_input_progress);
+        mSendButton = findViewById(R.id.remote_input_send);
+        mSendButton.setOnClickListener(this);
+
+        mEditText = (RemoteEditText) getChildAt(0);
+        mEditText.setOnEditorActionListener(new TextView.OnEditorActionListener() {
+            @Override
+            public boolean onEditorAction(TextView v, int actionId, KeyEvent event) {
+                final boolean isSoftImeEvent = event == null
+                        && (actionId == EditorInfo.IME_ACTION_DONE
+                                || actionId == EditorInfo.IME_ACTION_NEXT
+                                || actionId == EditorInfo.IME_ACTION_SEND);
+                final boolean isKeyboardEnterKey = event != null
+                        && KeyEvent.isConfirmKey(event.getKeyCode())
+                        && event.getAction() == KeyEvent.ACTION_DOWN;
+
+                if (isSoftImeEvent || isKeyboardEnterKey) {
+                    if (mEditText.length() > 0) {
+                        sendRemoteInput();
+                    }
+                    // Consume action to prevent IME from closing.
+                    return true;
+                }
+                return false;
+            }
+        });
+        mEditText.addTextChangedListener(this);
+        mEditText.setInnerFocusable(false);
+        mEditText.mRemoteInputView = this;
+    }
+
+    private void sendRemoteInput() {
+        Bundle results = new Bundle();
+        results.putString(mRemoteInput.getResultKey(), mEditText.getText().toString());
+        Intent fillInIntent = new Intent().addFlags(Intent.FLAG_RECEIVER_FOREGROUND);
+        RemoteInput.addResultsToIntent(mRemoteInputs, fillInIntent,
+                results);
+
+        mEditText.setEnabled(false);
+        mSendButton.setVisibility(INVISIBLE);
+        mProgressBar.setVisibility(VISIBLE);
+        mEditText.mShowImeOnInputConnection = false;
+
+        // Tell ShortcutManager that this package has been "activated".  ShortcutManager
+        // will reset the throttling for this package.
+        // Strictly speaking, the intent receiver may be different from the intent creator,
+        // but that's an edge case, and also because we can't always know which package will receive
+        // an intent, so we just reset for the creator.
+        getContext().getSystemService(ShortcutManager.class).onApplicationActive(
+                mPendingIntent.getCreatorPackage(),
+                getContext().getUserId());
+
+        try {
+            mPendingIntent.send(mContext, 0, fillInIntent);
+            reset();
+        } catch (PendingIntent.CanceledException e) {
+            Log.i(TAG, "Unable to send remote input result", e);
+            Toast.makeText(mContext, "Failure sending pending intent for inline reply :(",
+                    Toast.LENGTH_SHORT).show();
+            reset();
+        }
+    }
+
+    /**
+     * Creates a remote input view.
+     */
+    public static RemoteInputView inflate(Context context, ViewGroup root) {
+        RemoteInputView v = (RemoteInputView) LayoutInflater.from(context).inflate(
+                R.layout.slice_remote_input, root, false);
+        v.setTag(VIEW_TAG);
+        return v;
+    }
+
+    @Override
+    public void onClick(View v) {
+        if (v == mSendButton) {
+            sendRemoteInput();
+        }
+    }
+
+    @Override
+    public boolean onTouchEvent(MotionEvent event) {
+        super.onTouchEvent(event);
+
+        // We never want for a touch to escape to an outer view or one we covered.
+        return true;
+    }
+
+    private void onDefocus() {
+        setVisibility(INVISIBLE);
+    }
+
+    /**
+     * Set the pending intent for remote input.
+     */
+    public void setPendingIntent(PendingIntent pendingIntent) {
+        mPendingIntent = pendingIntent;
+    }
+
+    /**
+     * Set the remote inputs for this view.
+     */
+    public void setRemoteInput(RemoteInput[] remoteInputs, RemoteInput remoteInput) {
+        mRemoteInputs = remoteInputs;
+        mRemoteInput = remoteInput;
+        mEditText.setHint(mRemoteInput.getLabel());
+    }
+
+    /**
+     * Focuses the remote input view.
+     */
+    public void focusAnimated() {
+        if (getVisibility() != VISIBLE) {
+            Animator animator = ViewAnimationUtils.createCircularReveal(
+                    this, mRevealCx, mRevealCy, 0, mRevealR);
+            animator.setDuration(200);
+            animator.start();
+        }
+        focus();
+    }
+
+    private void focus() {
+        setVisibility(VISIBLE);
+        mEditText.setInnerFocusable(true);
+        mEditText.mShowImeOnInputConnection = true;
+        mEditText.setSelection(mEditText.getText().length());
+        mEditText.requestFocus();
+        updateSendButton();
+    }
+
+    private void reset() {
+        mResetting = true;
+
+        mEditText.getText().clear();
+        mEditText.setEnabled(true);
+        mSendButton.setVisibility(VISIBLE);
+        mProgressBar.setVisibility(INVISIBLE);
+        updateSendButton();
+        onDefocus();
+
+        mResetting = false;
+    }
+
+    @Override
+    public boolean onRequestSendAccessibilityEvent(View child, AccessibilityEvent event) {
+        if (mResetting && child == mEditText) {
+            // Suppress text events if it happens during resetting. Ideally this would be
+            // suppressed by the text view not being shown, but that doesn't work here because it
+            // needs to stay visible for the animation.
+            return false;
+        }
+        return super.onRequestSendAccessibilityEvent(child, event);
+    }
+
+    private void updateSendButton() {
+        mSendButton.setEnabled(mEditText.getText().length() != 0);
+    }
+
+    @Override
+    public void beforeTextChanged(CharSequence s, int start, int count, int after) {
+    }
+
+    @Override
+    public void onTextChanged(CharSequence s, int start, int before, int count) {
+    }
+
+    @Override
+    public void afterTextChanged(Editable s) {
+        updateSendButton();
+    }
+
+    /**
+     * Tries to find an action that matches the current pending intent of this view and updates its
+     * state to that of the found action
+     *
+     * @return true if a matching action was found, false otherwise
+     */
+    public boolean updatePendingIntentFromActions(Notification.Action[] actions) {
+        if (mPendingIntent == null || actions == null) {
+            return false;
+        }
+        Intent current = mPendingIntent.getIntent();
+        if (current == null) {
+            return false;
+        }
+
+        for (Notification.Action a : actions) {
+            RemoteInput[] inputs = a.getRemoteInputs();
+            if (a.actionIntent == null || inputs == null) {
+                continue;
+            }
+            Intent candidate = a.actionIntent.getIntent();
+            if (!current.filterEquals(candidate)) {
+                continue;
+            }
+
+            RemoteInput input = null;
+            for (RemoteInput i : inputs) {
+                if (i.getAllowFreeFormInput()) {
+                    input = i;
+                }
+            }
+            if (input == null) {
+                continue;
+            }
+            setPendingIntent(a.actionIntent);
+            setRemoteInput(inputs, input);
+            return true;
+        }
+        return false;
+    }
+
+    /**
+     * @hide
+     */
+    public void setRevealParameters(int cx, int cy, int r) {
+        mRevealCx = cx;
+        mRevealCy = cy;
+        mRevealR = r;
+    }
+
+    @Override
+    public void dispatchStartTemporaryDetach() {
+        super.dispatchStartTemporaryDetach();
+        // Detach the EditText temporarily such that it doesn't get onDetachedFromWindow and
+        // won't lose IME focus.
+        detachViewFromParent(mEditText);
+    }
+
+    @Override
+    public void dispatchFinishTemporaryDetach() {
+        if (isAttachedToWindow()) {
+            attachViewToParent(mEditText, 0, mEditText.getLayoutParams());
+        } else {
+            removeDetachedView(mEditText, false /* animate */);
+        }
+        super.dispatchFinishTemporaryDetach();
+    }
+
+    /**
+     * An EditText that changes appearance based on whether it's focusable and becomes un-focusable
+     * whenever the user navigates away from it or it becomes invisible.
+     */
+    public static class RemoteEditText extends EditText {
+
+        private final Drawable mBackground;
+        private RemoteInputView mRemoteInputView;
+        boolean mShowImeOnInputConnection;
+
+        public RemoteEditText(Context context, AttributeSet attrs) {
+            super(context, attrs);
+            mBackground = getBackground();
+        }
+
+        private void defocusIfNeeded(boolean animate) {
+            if (mRemoteInputView != null || isTemporarilyDetached()) {
+                if (isTemporarilyDetached()) {
+                    // We might get reattached but then the other one of HUN / expanded might steal
+                    // our focus, so we'll need to save our text here.
+                }
+                return;
+            }
+            if (isFocusable() && isEnabled()) {
+                setInnerFocusable(false);
+                if (mRemoteInputView != null) {
+                    mRemoteInputView.onDefocus();
+                }
+                mShowImeOnInputConnection = false;
+            }
+        }
+
+        @Override
+        protected void onVisibilityChanged(View changedView, int visibility) {
+            super.onVisibilityChanged(changedView, visibility);
+
+            if (!isShown()) {
+                defocusIfNeeded(false /* animate */);
+            }
+        }
+
+        @Override
+        protected void onFocusChanged(boolean focused, int direction, Rect previouslyFocusedRect) {
+            super.onFocusChanged(focused, direction, previouslyFocusedRect);
+            if (!focused) {
+                defocusIfNeeded(true /* animate */);
+            }
+        }
+
+        @Override
+        public void getFocusedRect(Rect r) {
+            super.getFocusedRect(r);
+            r.top = mScrollY;
+            r.bottom = mScrollY + (mBottom - mTop);
+        }
+
+        @Override
+        public boolean onKeyDown(int keyCode, KeyEvent event) {
+            if (keyCode == KeyEvent.KEYCODE_BACK) {
+                // Eat the DOWN event here to prevent any default behavior.
+                return true;
+            }
+            return super.onKeyDown(keyCode, event);
+        }
+
+        @Override
+        public boolean onKeyUp(int keyCode, KeyEvent event) {
+            if (keyCode == KeyEvent.KEYCODE_BACK) {
+                defocusIfNeeded(true /* animate */);
+                return true;
+            }
+            return super.onKeyUp(keyCode, event);
+        }
+
+        @Override
+        public InputConnection onCreateInputConnection(EditorInfo outAttrs) {
+            final InputConnection inputConnection = super.onCreateInputConnection(outAttrs);
+
+            if (mShowImeOnInputConnection && inputConnection != null) {
+                final InputMethodManager imm = InputMethodManager.getInstance();
+                if (imm != null) {
+                    // onCreateInputConnection is called by InputMethodManager in the middle of
+                    // setting up the connection to the IME; wait with requesting the IME until that
+                    // work has completed.
+                    post(new Runnable() {
+                        @Override
+                        public void run() {
+                            imm.viewClicked(RemoteEditText.this);
+                            imm.showSoftInput(RemoteEditText.this, 0);
+                        }
+                    });
+                }
+            }
+
+            return inputConnection;
+        }
+
+        @Override
+        public void onCommitCompletion(CompletionInfo text) {
+            clearComposingText();
+            setText(text.getText());
+            setSelection(getText().length());
+        }
+
+        void setInnerFocusable(boolean focusable) {
+            setFocusableInTouchMode(focusable);
+            setFocusable(focusable);
+            setCursorVisible(focusable);
+
+            if (focusable) {
+                requestFocus();
+                setBackground(mBackground);
+            } else {
+                setBackground(null);
+            }
+
+        }
+    }
+}
diff --git a/core/java/android/slice/views/ShortcutView.java b/core/java/android/slice/views/ShortcutView.java
new file mode 100644
index 0000000..8fe2f1a
--- /dev/null
+++ b/core/java/android/slice/views/ShortcutView.java
@@ -0,0 +1,110 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.slice.views;
+
+import android.app.PendingIntent;
+import android.app.PendingIntent.CanceledException;
+import android.content.Context;
+import android.content.Intent;
+import android.graphics.Color;
+import android.graphics.drawable.ShapeDrawable;
+import android.graphics.drawable.shapes.OvalShape;
+import android.net.Uri;
+import android.slice.Slice;
+import android.slice.SliceItem;
+import android.slice.SliceQuery;
+import android.slice.views.SliceView.SliceModeView;
+import android.view.ViewGroup;
+
+import com.android.internal.R;
+
+/**
+ * @hide
+ */
+public class ShortcutView extends SliceModeView {
+
+    private static final String TAG = "ShortcutView";
+
+    private PendingIntent mAction;
+    private Uri mUri;
+    private int mLargeIconSize;
+    private int mSmallIconSize;
+
+    public ShortcutView(Context context) {
+        super(context);
+        mLargeIconSize = getContext().getResources()
+                .getDimensionPixelSize(R.dimen.slice_shortcut_size);
+        mSmallIconSize = getContext().getResources().getDimensionPixelSize(R.dimen.slice_icon_size);
+        setLayoutParams(new ViewGroup.LayoutParams(mLargeIconSize, mLargeIconSize));
+    }
+
+    @Override
+    public void setSlice(Slice slice) {
+        removeAllViews();
+        SliceItem sliceItem = SliceQuery.find(slice, SliceItem.TYPE_ACTION);
+        SliceItem iconItem = slice.getPrimaryIcon();
+        SliceItem textItem = sliceItem != null
+                ? SliceQuery.find(sliceItem, SliceItem.TYPE_TEXT)
+                : SliceQuery.find(slice, SliceItem.TYPE_TEXT);
+        SliceItem colorItem = sliceItem != null
+                ? SliceQuery.find(sliceItem, SliceItem.TYPE_COLOR)
+                : SliceQuery.find(slice, SliceItem.TYPE_COLOR);
+        if (colorItem == null) {
+            colorItem = SliceQuery.find(slice, SliceItem.TYPE_COLOR);
+        }
+        // TODO: pick better default colour
+        final int color = colorItem != null ? colorItem.getColor() : Color.GRAY;
+        ShapeDrawable circle = new ShapeDrawable(new OvalShape());
+        circle.setTint(color);
+        setBackground(circle);
+        if (iconItem != null) {
+            final boolean isLarge = iconItem.hasHint(Slice.HINT_LARGE);
+            final int iconSize = isLarge ? mLargeIconSize : mSmallIconSize;
+            SliceViewUtil.createCircledIcon(getContext(), color, iconSize, iconItem.getIcon(),
+                    isLarge, this /* parent */);
+            mAction = sliceItem != null ? sliceItem.getAction()
+                    : null;
+            mUri = slice.getUri();
+            setClickable(true);
+        } else {
+            setClickable(false);
+        }
+    }
+
+    @Override
+    public String getMode() {
+        return SliceView.MODE_SHORTCUT;
+    }
+
+    @Override
+    public boolean performClick() {
+        if (!callOnClick()) {
+            try {
+                if (mAction != null) {
+                    mAction.send();
+                } else {
+                    Intent intent = new Intent(Intent.ACTION_VIEW).setData(mUri);
+                    intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
+                    getContext().startActivity(intent);
+                }
+            } catch (CanceledException e) {
+                e.printStackTrace();
+            }
+        }
+        return true;
+    }
+}
diff --git a/core/java/android/slice/views/SliceView.java b/core/java/android/slice/views/SliceView.java
new file mode 100644
index 0000000..f379248
--- /dev/null
+++ b/core/java/android/slice/views/SliceView.java
@@ -0,0 +1,249 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.slice.views;
+
+import android.annotation.StringDef;
+import android.content.ContentResolver;
+import android.content.Context;
+import android.content.Intent;
+import android.graphics.drawable.ColorDrawable;
+import android.net.Uri;
+import android.slice.Slice;
+import android.slice.SliceItem;
+import android.slice.SliceQuery;
+import android.util.Log;
+import android.view.MotionEvent;
+import android.view.View;
+import android.widget.FrameLayout;
+import android.widget.LinearLayout;
+
+/**
+ * A view that can display a {@link Slice} in different {@link SliceMode}'s.
+ *
+ * @hide
+ */
+public class SliceView extends LinearLayout {
+
+    private static final String TAG = "SliceView";
+
+    /**
+     * @hide
+     */
+    public abstract static class SliceModeView extends FrameLayout {
+
+        public SliceModeView(Context context) {
+            super(context);
+        }
+
+        /**
+         * @return the {@link SliceMode} of the slice being presented.
+         */
+        public abstract String getMode();
+
+        /**
+         * @param slice the slice to show in this view.
+         */
+        public abstract void setSlice(Slice slice);
+    }
+
+    /**
+     * @hide
+     */
+    @StringDef({
+            MODE_SMALL, MODE_LARGE, MODE_SHORTCUT
+    })
+    public @interface SliceMode {}
+
+    /**
+     * Mode indicating this slice should be presented in small template format.
+     */
+    public static final String MODE_SMALL       = "SLICE_SMALL";
+    /**
+     * Mode indicating this slice should be presented in large template format.
+     */
+    public static final String MODE_LARGE       = "SLICE_LARGE";
+    /**
+     * Mode indicating this slice should be presented as an icon.
+     */
+    public static final String MODE_SHORTCUT    = "SLICE_ICON";
+
+    /**
+     * Will select the type of slice binding based on size of the View. TODO: Put in some info about
+     * that selection.
+     */
+    private static final String MODE_AUTO = "auto";
+
+    private String mMode = MODE_AUTO;
+    private SliceModeView mCurrentView;
+    private final ActionRow mActions;
+    private Slice mCurrentSlice;
+    private boolean mShowActions = true;
+
+    /**
+     * Simple constructor to create a slice view from code.
+     *
+     * @param context The context the view is running in.
+     */
+    public SliceView(Context context) {
+        super(context);
+        setOrientation(LinearLayout.VERTICAL);
+        mActions = new ActionRow(mContext, true);
+        mActions.setBackground(new ColorDrawable(0xffeeeeee));
+        mCurrentView = new LargeTemplateView(mContext);
+        addView(mCurrentView);
+        addView(mActions);
+    }
+
+    /**
+     * @hide
+     */
+    public void bindSlice(Intent intent) {
+        // TODO
+    }
+
+    /**
+     * Binds this view to the {@link Slice} associated with the provided {@link Uri}.
+     */
+    public void bindSlice(Uri sliceUri) {
+        validate(sliceUri);
+        Slice s = mContext.getContentResolver().bindSlice(sliceUri);
+        bindSlice(s);
+    }
+
+    /**
+     * Binds this view to the provided {@link Slice}.
+     */
+    public void bindSlice(Slice slice) {
+        mCurrentSlice = slice;
+        if (mCurrentSlice != null) {
+            reinflate();
+        }
+    }
+
+    /**
+     * Call to clean up the view.
+     */
+    public void unbindSlice() {
+        mCurrentSlice = null;
+    }
+
+    /**
+     * Set the {@link SliceMode} this view should present in.
+     */
+    public void setMode(@SliceMode String mode) {
+        setMode(mode, false /* animate */);
+    }
+
+    /**
+     * @hide
+     */
+    public void setMode(@SliceMode String mode, boolean animate) {
+        if (animate) {
+            Log.e(TAG, "Animation not supported yet");
+        }
+        mMode = mode;
+        reinflate();
+    }
+
+    /**
+     * @return the {@link SliceMode} this view is presenting in.
+     */
+    public @SliceMode String getMode() {
+        if (mMode.equals(MODE_AUTO)) {
+            return MODE_LARGE;
+        }
+        return mMode;
+    }
+
+    /**
+     * @hide
+     *
+     * Whether this view should show a row of actions with it.
+     */
+    public void setShowActionRow(boolean show) {
+        mShowActions = show;
+        reinflate();
+    }
+
+    private SliceModeView createView(String mode) {
+        switch (mode) {
+            case MODE_SHORTCUT:
+                return new ShortcutView(getContext());
+            case MODE_SMALL:
+                return new SmallTemplateView(getContext());
+        }
+        return new LargeTemplateView(getContext());
+    }
+
+    @Override
+    protected void onDetachedFromWindow() {
+        super.onDetachedFromWindow();
+        unbindSlice();
+    }
+
+    private void reinflate() {
+        if (mCurrentSlice == null) {
+            return;
+        }
+        // TODO: Smarter mapping here from one state to the next.
+        SliceItem color = SliceQuery.find(mCurrentSlice, SliceItem.TYPE_COLOR);
+        SliceItem[] items = mCurrentSlice.getItems();
+        SliceItem actionRow = SliceQuery.find(mCurrentSlice, SliceItem.TYPE_SLICE,
+                Slice.HINT_ACTIONS,
+                Slice.HINT_ALT);
+        String mode = getMode();
+        if (!mode.equals(mCurrentView.getMode())) {
+            removeAllViews();
+            mCurrentView = createView(mode);
+            addView(mCurrentView);
+            addView(mActions);
+        }
+        if (items.length > 1 || (items.length != 0 && items[0] != actionRow)) {
+            mCurrentView.setVisibility(View.VISIBLE);
+            mCurrentView.setSlice(mCurrentSlice);
+        } else {
+            mCurrentView.setVisibility(View.GONE);
+        }
+
+        boolean showActions = mShowActions && actionRow != null
+                && !mode.equals(MODE_SHORTCUT);
+        if (showActions) {
+            mActions.setActions(actionRow, color);
+            mActions.setVisibility(View.VISIBLE);
+        } else {
+            mActions.setVisibility(View.GONE);
+        }
+    }
+
+    @Override
+    public boolean onInterceptTouchEvent(MotionEvent ev) {
+        // TODO -- may need to rethink for AGSA
+        if (ev.getActionMasked() == MotionEvent.ACTION_DOWN) {
+            requestDisallowInterceptTouchEvent(true);
+        }
+        return super.onInterceptTouchEvent(ev);
+    }
+
+    private static void validate(Uri sliceUri) {
+        if (!ContentResolver.SCHEME_SLICE.equals(sliceUri.getScheme())) {
+            throw new RuntimeException("Invalid uri " + sliceUri);
+        }
+        if (sliceUri.getPathSegments().size() == 0) {
+            throw new RuntimeException("Invalid uri " + sliceUri);
+        }
+    }
+}
diff --git a/core/java/android/slice/views/SliceViewUtil.java b/core/java/android/slice/views/SliceViewUtil.java
new file mode 100644
index 0000000..1b5a6d1
--- /dev/null
+++ b/core/java/android/slice/views/SliceViewUtil.java
@@ -0,0 +1,182 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.slice.views;
+
+import android.annotation.ColorInt;
+import android.content.Context;
+import android.content.res.ColorStateList;
+import android.content.res.TypedArray;
+import android.graphics.Bitmap;
+import android.graphics.Bitmap.Config;
+import android.graphics.Canvas;
+import android.graphics.Color;
+import android.graphics.Paint;
+import android.graphics.PorterDuff.Mode;
+import android.graphics.PorterDuffXfermode;
+import android.graphics.Rect;
+import android.graphics.drawable.Drawable;
+import android.graphics.drawable.Icon;
+import android.view.Gravity;
+import android.view.ViewGroup;
+import android.widget.FrameLayout;
+import android.widget.ImageView;
+
+/**
+ * A bunch of utilities for slice UI.
+ *
+ * @hide
+ */
+public class SliceViewUtil {
+
+    /**
+     * @hide
+     */
+    @ColorInt
+    public static int getColorAccent(Context context) {
+        return getColorAttr(context, android.R.attr.colorAccent);
+    }
+
+    /**
+     * @hide
+     */
+    @ColorInt
+    public static int getColorError(Context context) {
+        return getColorAttr(context, android.R.attr.colorError);
+    }
+
+    /**
+     * @hide
+     */
+    @ColorInt
+    public static int getDefaultColor(Context context, int resId) {
+        final ColorStateList list = context.getResources().getColorStateList(resId,
+                context.getTheme());
+
+        return list.getDefaultColor();
+    }
+
+    /**
+     * @hide
+     */
+    @ColorInt
+    public static int getDisabled(Context context, int inputColor) {
+        return applyAlphaAttr(context, android.R.attr.disabledAlpha, inputColor);
+    }
+
+    /**
+     * @hide
+     */
+    @ColorInt
+    public static int applyAlphaAttr(Context context, int attr, int inputColor) {
+        TypedArray ta = context.obtainStyledAttributes(new int[] {
+                attr
+        });
+        float alpha = ta.getFloat(0, 0);
+        ta.recycle();
+        return applyAlpha(alpha, inputColor);
+    }
+
+    /**
+     * @hide
+     */
+    @ColorInt
+    public static int applyAlpha(float alpha, int inputColor) {
+        alpha *= Color.alpha(inputColor);
+        return Color.argb((int) (alpha), Color.red(inputColor), Color.green(inputColor),
+                Color.blue(inputColor));
+    }
+
+    /**
+     * @hide
+     */
+    @ColorInt
+    public static int getColorAttr(Context context, int attr) {
+        TypedArray ta = context.obtainStyledAttributes(new int[] {
+                attr
+        });
+        @ColorInt
+        int colorAccent = ta.getColor(0, 0);
+        ta.recycle();
+        return colorAccent;
+    }
+
+    /**
+     * @hide
+     */
+    public static int getThemeAttr(Context context, int attr) {
+        TypedArray ta = context.obtainStyledAttributes(new int[] {
+                attr
+        });
+        int theme = ta.getResourceId(0, 0);
+        ta.recycle();
+        return theme;
+    }
+
+    /**
+     * @hide
+     */
+    public static Drawable getDrawable(Context context, int attr) {
+        TypedArray ta = context.obtainStyledAttributes(new int[] {
+                attr
+        });
+        Drawable drawable = ta.getDrawable(0);
+        ta.recycle();
+        return drawable;
+    }
+
+    /**
+     * @hide
+     */
+    public static void createCircledIcon(Context context, int color, int iconSize, Icon icon,
+            boolean isLarge, ViewGroup parent) {
+        ImageView v = new ImageView(context);
+        v.setImageIcon(icon);
+        parent.addView(v);
+        FrameLayout.LayoutParams lp = (FrameLayout.LayoutParams) v.getLayoutParams();
+        if (isLarge) {
+            // XXX better way to convert from icon -> bitmap or crop an icon (?)
+            Bitmap iconBm = Bitmap.createBitmap(iconSize, iconSize, Bitmap.Config.ARGB_8888);
+            Canvas iconCanvas = new Canvas(iconBm);
+            v.layout(0, 0, iconSize, iconSize);
+            v.draw(iconCanvas);
+            v.setImageBitmap(getCircularBitmap(iconBm));
+        } else {
+            v.setColorFilter(Color.WHITE);
+        }
+        lp.width = iconSize;
+        lp.height = iconSize;
+        lp.gravity = Gravity.CENTER;
+    }
+
+    /**
+     * @hide
+     */
+    public static Bitmap getCircularBitmap(Bitmap bitmap) {
+        Bitmap output = Bitmap.createBitmap(bitmap.getWidth(),
+                bitmap.getHeight(), Config.ARGB_8888);
+        Canvas canvas = new Canvas(output);
+        final Paint paint = new Paint();
+        final Rect rect = new Rect(0, 0, bitmap.getWidth(), bitmap.getHeight());
+        paint.setAntiAlias(true);
+        canvas.drawARGB(0, 0, 0, 0);
+        canvas.drawCircle(bitmap.getWidth() / 2, bitmap.getHeight() / 2,
+                bitmap.getWidth() / 2, paint);
+        paint.setXfermode(new PorterDuffXfermode(Mode.SRC_IN));
+        canvas.drawBitmap(bitmap, rect, rect, paint);
+        return output;
+    }
+}
diff --git a/core/java/android/slice/views/SmallTemplateView.java b/core/java/android/slice/views/SmallTemplateView.java
new file mode 100644
index 0000000..b0b181e
--- /dev/null
+++ b/core/java/android/slice/views/SmallTemplateView.java
@@ -0,0 +1,211 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.slice.views;
+
+import android.app.PendingIntent.CanceledException;
+import android.content.Context;
+import android.os.AsyncTask;
+import android.slice.Slice;
+import android.slice.SliceItem;
+import android.slice.SliceQuery;
+import android.slice.views.LargeSliceAdapter.SliceListView;
+import android.slice.views.SliceView.SliceModeView;
+import android.view.View;
+import android.widget.ImageView;
+import android.widget.LinearLayout;
+import android.widget.TextView;
+
+import com.android.internal.R;
+
+import java.text.Format;
+import java.text.SimpleDateFormat;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Date;
+import java.util.List;
+
+/**
+ * Small template is also used to construct list items for use with {@link LargeTemplateView}.
+ *
+ * @hide
+ */
+public class SmallTemplateView extends SliceModeView implements SliceListView {
+
+    private static final String TAG = "SmallTemplateView";
+
+    private int mIconSize;
+    private int mPadding;
+
+    private LinearLayout mStartContainer;
+    private TextView mTitleText;
+    private TextView mSecondaryText;
+    private LinearLayout mEndContainer;
+
+    public SmallTemplateView(Context context) {
+        super(context);
+        inflate(context, R.layout.slice_small_template, this);
+        mIconSize = getContext().getResources().getDimensionPixelSize(R.dimen.slice_icon_size);
+        mPadding = getContext().getResources().getDimensionPixelSize(R.dimen.slice_padding);
+
+        mStartContainer = (LinearLayout) findViewById(android.R.id.icon_frame);
+        mTitleText = (TextView) findViewById(android.R.id.title);
+        mSecondaryText = (TextView) findViewById(android.R.id.summary);
+        mEndContainer = (LinearLayout) findViewById(android.R.id.widget_frame);
+    }
+
+    @Override
+    public String getMode() {
+        return SliceView.MODE_SMALL;
+    }
+
+    @Override
+    public void setSliceItem(SliceItem slice) {
+        resetViews();
+        SliceItem colorItem = SliceQuery.find(slice, SliceItem.TYPE_COLOR);
+        int color = colorItem != null ? colorItem.getColor() : -1;
+
+        // Look for any title elements
+        List<SliceItem> titleItems = SliceQuery.findAll(slice, -1, Slice.HINT_TITLE,
+                null);
+        boolean hasTitleText = false;
+        boolean hasTitleItem = false;
+        for (int i = 0; i < titleItems.size(); i++) {
+            SliceItem item = titleItems.get(i);
+            if (!hasTitleItem) {
+                // icon, action icon, or timestamp
+                if (item.getType() == SliceItem.TYPE_ACTION) {
+                    hasTitleItem = addIcon(item, color, mStartContainer);
+                } else if (item.getType() == SliceItem.TYPE_IMAGE) {
+                    addIcon(item, color, mStartContainer);
+                    hasTitleItem = true;
+                } else if (item.getType() == SliceItem.TYPE_TIMESTAMP) {
+                    TextView tv = new TextView(getContext());
+                    tv.setText(convertTimeToString(item.getTimestamp()));
+                    hasTitleItem = true;
+                }
+            }
+            if (!hasTitleText && item.getType() == SliceItem.TYPE_TEXT) {
+                mTitleText.setText(item.getText());
+                hasTitleText = true;
+            }
+            if (hasTitleText && hasTitleItem) {
+                break;
+            }
+        }
+        mTitleText.setVisibility(hasTitleText ? View.VISIBLE : View.GONE);
+        mStartContainer.setVisibility(hasTitleItem ? View.VISIBLE : View.GONE);
+
+        if (slice.getType() != SliceItem.TYPE_SLICE) {
+            return;
+        }
+
+        // Deal with remaining items
+        int itemCount = 0;
+        boolean hasSummary = false;
+        ArrayList<SliceItem> sliceItems = new ArrayList<SliceItem>(
+                Arrays.asList(slice.getSlice().getItems()));
+        for (int i = 0; i < sliceItems.size(); i++) {
+            SliceItem item = sliceItems.get(i);
+            if (!hasSummary && item.getType() == SliceItem.TYPE_TEXT
+                    && !item.hasHint(Slice.HINT_TITLE)) {
+                // TODO -- Should combine all text items?
+                mSecondaryText.setText(item.getText());
+                hasSummary = true;
+            }
+            if (itemCount <= 3) {
+                if (item.getType() == SliceItem.TYPE_ACTION) {
+                    if (addIcon(item, color, mEndContainer)) {
+                        itemCount++;
+                    }
+                } else if (item.getType() == SliceItem.TYPE_IMAGE) {
+                    addIcon(item, color, mEndContainer);
+                    itemCount++;
+                } else if (item.getType() == SliceItem.TYPE_TIMESTAMP) {
+                    TextView tv = new TextView(getContext());
+                    tv.setText(convertTimeToString(item.getTimestamp()));
+                    mEndContainer.addView(tv);
+                    itemCount++;
+                } else if (item.getType() == SliceItem.TYPE_SLICE) {
+                    SliceItem[] subItems = item.getSlice().getItems();
+                    for (int j = 0; j < subItems.length; j++) {
+                        sliceItems.add(subItems[j]);
+                    }
+                }
+            }
+        }
+    }
+
+    @Override
+    public void setSlice(Slice slice) {
+        setSliceItem(new SliceItem(slice, SliceItem.TYPE_SLICE, slice.getHints()));
+    }
+
+    /**
+     * @return Whether an icon was added.
+     */
+    private boolean addIcon(SliceItem sliceItem, int color, LinearLayout container) {
+        SliceItem image = null;
+        SliceItem action = null;
+        if (sliceItem.getType() == SliceItem.TYPE_ACTION) {
+            image = SliceQuery.find(sliceItem.getSlice(), SliceItem.TYPE_IMAGE);
+            action = sliceItem;
+        } else if (sliceItem.getType() == SliceItem.TYPE_IMAGE) {
+            image = sliceItem;
+        }
+        if (image != null) {
+            ImageView iv = new ImageView(getContext());
+            iv.setImageIcon(image.getIcon());
+            if (action != null) {
+                final SliceItem sliceAction = action;
+                iv.setOnClickListener(v -> AsyncTask.execute(
+                        () -> {
+                            try {
+                                sliceAction.getAction().send();
+                            } catch (CanceledException e) {
+                                e.printStackTrace();
+                            }
+                        }));
+                iv.setBackground(SliceViewUtil.getDrawable(getContext(),
+                        android.R.attr.selectableItemBackground));
+            }
+            if (color != -1 && !sliceItem.hasHint(Slice.HINT_NO_TINT)) {
+                iv.setColorFilter(color);
+            }
+            container.addView(iv);
+            LinearLayout.LayoutParams lp = (LinearLayout.LayoutParams) iv.getLayoutParams();
+            lp.width = mIconSize;
+            lp.height = mIconSize;
+            lp.setMarginStart(mPadding);
+            return true;
+        }
+        return false;
+    }
+
+    private String convertTimeToString(long time) {
+        // TODO -- figure out what format(s) we support
+        Date date = new Date(time);
+        Format format = new SimpleDateFormat("MM dd yyyy HH:mm:ss");
+        return format.format(date);
+    }
+
+    private void resetViews() {
+        mStartContainer.removeAllViews();
+        mEndContainer.removeAllViews();
+        mTitleText.setText(null);
+        mSecondaryText.setText(null);
+    }
+}
diff --git a/core/res/res/drawable/ic_slice_send.xml b/core/res/res/drawable/ic_slice_send.xml
new file mode 100644
index 0000000..b8b6954
--- /dev/null
+++ b/core/res/res/drawable/ic_slice_send.xml
@@ -0,0 +1,24 @@
+<!-- Copyright (C) 2017 The Android Open Source Project
+
+     Licensed under the Apache License, Version 2.0 (the "License");
+     you may not use this file except in compliance with the License.
+     You may obtain a copy of the License at
+
+          http://www.apache.org/licenses/LICENSE-2.0
+
+     Unless required by applicable law or agreed to in writing, software
+     distributed under the License is distributed on an "AS IS" BASIS,
+     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+     See the License for the specific language governing permissions and
+     limitations under the License.
+-->
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+        android:autoMirrored="true"
+        android:width="24.0dp"
+        android:height="24.0dp"
+        android:viewportWidth="48.0"
+        android:viewportHeight="48.0">
+    <path
+        android:fillColor="#FF000000"
+        android:pathData="M4.02,42.0L46.0,24.0 4.02,6.0 4.0,20.0l30.0,4.0 -30.0,4.0z"/>
+</vector>
\ No newline at end of file
diff --git a/core/res/res/drawable/slice_remote_input_bg.xml b/core/res/res/drawable/slice_remote_input_bg.xml
new file mode 100644
index 0000000..3120679
--- /dev/null
+++ b/core/res/res/drawable/slice_remote_input_bg.xml
@@ -0,0 +1,21 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2016 The Android Open Source Project
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+  http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+-->
+<shape xmlns:android="http://schemas.android.com/apk/res/android">
+    <solid android:color="#ff6c6c6c" />
+    <corners
+        android:bottomRightRadius="16dp"
+        android:bottomLeftRadius="16dp"/>
+</shape>
diff --git a/core/res/res/drawable/slice_ripple_drawable.xml b/core/res/res/drawable/slice_ripple_drawable.xml
new file mode 100644
index 0000000..5ba1f07
--- /dev/null
+++ b/core/res/res/drawable/slice_ripple_drawable.xml
@@ -0,0 +1,19 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  ~ Copyright (C) 2014 The Android Open Source Project
+  ~
+  ~ Licensed under the Apache License, Version 2.0 (the "License");
+  ~ you may not use this file except in compliance with the License.
+  ~ You may obtain a copy of the License at
+  ~
+  ~      http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing, software
+  ~ distributed under the License is distributed on an "AS IS" BASIS,
+  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  ~ See the License for the specific language governing permissions and
+  ~ limitations under the License
+  -->
+
+<ripple xmlns:android="http://schemas.android.com/apk/res/android"
+    android:color="?android:attr/colorControlHighlight" />
\ No newline at end of file
diff --git a/core/res/res/layout/slice_grid.xml b/core/res/res/layout/slice_grid.xml
new file mode 100644
index 0000000..70df76b
--- /dev/null
+++ b/core/res/res/layout/slice_grid.xml
@@ -0,0 +1,24 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2017 The Android Open Source Project
+
+     Licensed under the Apache License, Version 2.0 (the "License");
+     you may not use this file except in compliance with the License.
+     You may obtain a copy of the License at
+
+          http://www.apache.org/licenses/LICENSE-2.0
+
+     Unless required by applicable law or agreed to in writing, software
+     distributed under the License is distributed on an "AS IS" BASIS,
+     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+     See the License for the specific language governing permissions and
+     limitations under the License.
+-->
+<android.slice.views.GridView
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    android:layout_width="match_parent"
+    android:layout_height="wrap_content"
+    android:minHeight="?android:attr/listPreferredItemHeightSmall"
+    android:gravity="center_vertical"
+    android:background="?android:attr/activatedBackgroundIndicator"
+    android:clipToPadding="false">
+</android.slice.views.GridView>
diff --git a/core/res/res/layout/slice_message.xml b/core/res/res/layout/slice_message.xml
new file mode 100644
index 0000000..a3279b6
--- /dev/null
+++ b/core/res/res/layout/slice_message.xml
@@ -0,0 +1,51 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2017 The Android Open Source Project
+
+     Licensed under the Apache License, Version 2.0 (the "License");
+     you may not use this file except in compliance with the License.
+     You may obtain a copy of the License at
+
+          http://www.apache.org/licenses/LICENSE-2.0
+
+     Unless required by applicable law or agreed to in writing, software
+     distributed under the License is distributed on an "AS IS" BASIS,
+     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+     See the License for the specific language governing permissions and
+     limitations under the License.
+-->
+<android.slice.views.MessageView
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    android:layout_width="match_parent"
+    android:layout_height="wrap_content"
+    android:paddingTop="12dp"
+    android:paddingStart="?android:attr/listPreferredItemPaddingStart"
+    android:paddingEnd="?android:attr/listPreferredItemPaddingEnd"
+    android:background="?android:attr/activatedBackgroundIndicator"
+    android:clipToPadding="false">
+
+    <LinearLayout
+        android:id="@android:id/icon_frame"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:layout_marginStart="-4dp"
+        android:gravity="start|center_vertical"
+        android:orientation="horizontal"
+        android:paddingEnd="12dp"
+        android:paddingTop="4dp"
+        android:paddingBottom="4dp">
+        <!-- TODO: Support text source -->
+        <ImageView
+            android:id="@android:id/icon"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:maxWidth="48dp"
+            android:maxHeight="48dp" />
+    </LinearLayout>
+
+    <TextView android:id="@android:id/summary"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:layout_alignStart="@android:id/title"
+        android:textAppearance="?android:attr/textAppearanceListItem"
+        android:maxLines="10" />
+</android.slice.views.MessageView>
diff --git a/core/res/res/layout/slice_message_local.xml b/core/res/res/layout/slice_message_local.xml
new file mode 100644
index 0000000..d4180f3
--- /dev/null
+++ b/core/res/res/layout/slice_message_local.xml
@@ -0,0 +1,38 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2017 The Android Open Source Project
+
+     Licensed under the Apache License, Version 2.0 (the "License");
+     you may not use this file except in compliance with the License.
+     You may obtain a copy of the License at
+
+          http://www.apache.org/licenses/LICENSE-2.0
+
+     Unless required by applicable law or agreed to in writing, software
+     distributed under the License is distributed on an "AS IS" BASIS,
+     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+     See the License for the specific language governing permissions and
+     limitations under the License.
+-->
+<android.slice.views.MessageView
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    android:layout_width="match_parent"
+    android:layout_height="wrap_content"
+    android:gravity="center_vertical|end"
+    android:paddingTop="12dp"
+    android:paddingStart="?android:attr/listPreferredItemPaddingStart"
+    android:paddingEnd="?android:attr/listPreferredItemPaddingEnd"
+    android:background="?android:attr/activatedBackgroundIndicator"
+    android:clipToPadding="false">
+
+    <TextView android:id="@android:id/summary"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:layout_alignStart="@android:id/title"
+        android:layout_gravity="end"
+        android:gravity="end"
+        android:padding="8dp"
+        android:textAppearance="?android:attr/textAppearanceListItem"
+        android:background="#ffeeeeee"
+        android:maxLines="10" />
+
+</android.slice.views.MessageView>
diff --git a/core/res/res/layout/slice_remote_input.xml b/core/res/res/layout/slice_remote_input.xml
new file mode 100644
index 0000000..dc570c4
--- /dev/null
+++ b/core/res/res/layout/slice_remote_input.xml
@@ -0,0 +1,76 @@
+<?xml version="1.0" encoding="utf-8"?>
+
+<!-- Copyright (C) 2017 The Android Open Source Project
+
+     Licensed under the Apache License, Version 2.0 (the "License");
+     you may not use this file except in compliance with the License.
+     You may obtain a copy of the License at
+
+          http://www.apache.org/licenses/LICENSE-2.0
+
+     Unless required by applicable law or agreed to in writing, software
+     distributed under the License is distributed on an "AS IS" BASIS,
+     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+     See the License for the specific language governing permissions and
+     limitations under the License.
+-->
+<!-- LinearLayout -->
+<android.slice.views.RemoteInputView
+        xmlns:android="http://schemas.android.com/apk/res/android"
+        android:id="@+id/remote_input"
+        android:background="@drawable/slice_remote_input_bg"
+        android:layout_height="match_parent"
+        android:layout_width="match_parent">
+
+    <view class="com.android.internal.slice.view.RemoteInputView$RemoteEditText"
+            android:id="@+id/remote_input_text"
+            android:layout_height="match_parent"
+            android:layout_width="0dp"
+            android:layout_weight="1"
+            android:paddingTop="2dp"
+            android:paddingBottom="4dp"
+            android:paddingStart="16dp"
+            android:paddingEnd="12dp"
+            android:gravity="start|center_vertical"
+            android:textAppearance="?android:attr/textAppearance"
+            android:textColor="#FFFFFFFF"
+            android:textColorHint="#99ffffff"
+            android:textSize="16sp"
+            android:background="@null"
+            android:singleLine="true"
+            android:ellipsize="start"
+            android:inputType="textShortMessage|textAutoCorrect|textCapSentences"
+            android:imeOptions="actionSend|flagNoExtractUi|flagNoFullscreen" />
+
+    <FrameLayout
+            android:layout_width="wrap_content"
+            android:layout_height="match_parent"
+            android:layout_gravity="center_vertical">
+
+        <ImageButton
+                android:layout_width="wrap_content"
+                android:layout_height="wrap_content"
+                android:layout_gravity="center"
+                android:paddingStart="12dp"
+                android:paddingEnd="24dp"
+                android:paddingTop="16dp"
+                android:paddingBottom="16dp"
+                android:id="@+id/remote_input_send"
+                android:src="@drawable/ic_slice_send"
+                android:tint="#FFFFFF"
+                android:tintMode="src_in"
+                android:background="@drawable/slice_ripple_drawable" />
+
+        <ProgressBar
+                android:id="@+id/remote_input_progress"
+                android:layout_width="24dp"
+                android:layout_height="24dp"
+                android:layout_marginEnd="6dp"
+                android:layout_gravity="center"
+                android:visibility="invisible"
+                android:indeterminate="true"
+                style="?android:attr/progressBarStyleSmall" />
+
+    </FrameLayout>
+
+</android.slice.views.RemoteInputView>
\ No newline at end of file
diff --git a/core/res/res/layout/slice_secondary_text.xml b/core/res/res/layout/slice_secondary_text.xml
new file mode 100644
index 0000000..80a1574
--- /dev/null
+++ b/core/res/res/layout/slice_secondary_text.xml
@@ -0,0 +1,25 @@
+<?xml version="1.0" encoding="utf-8"?>
+
+<!-- Copyright (C) 2017 The Android Open Source Project
+
+     Licensed under the Apache License, Version 2.0 (the "License");
+     you may not use this file except in compliance with the License.
+     You may obtain a copy of the License at
+
+          http://www.apache.org/licenses/LICENSE-2.0
+
+     Unless required by applicable law or agreed to in writing, software
+     distributed under the License is distributed on an "AS IS" BASIS,
+     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+     See the License for the specific language governing permissions and
+     limitations under the License.
+-->
+<!-- LinearLayout -->
+<TextView
+        xmlns:android="http://schemas.android.com/apk/res/android"
+        android:textAppearance="?android:attr/textAppearanceSmall"
+        android:textColor="?android:attr/textColorSecondary"
+        android:gravity="center"
+        android:layout_height="wrap_content"
+        android:padding="4dp"
+        android:layout_width="match_parent" />
diff --git a/core/res/res/layout/slice_small_template.xml b/core/res/res/layout/slice_small_template.xml
new file mode 100644
index 0000000..cced42b
--- /dev/null
+++ b/core/res/res/layout/slice_small_template.xml
@@ -0,0 +1,67 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2017 The Android Open Source Project
+
+     Licensed under the Apache License, Version 2.0 (the "License");
+     you may not use this file except in compliance with the License.
+     You may obtain a copy of the License at
+
+          http://www.apache.org/licenses/LICENSE-2.0
+
+     Unless required by applicable law or agreed to in writing, software
+     distributed under the License is distributed on an "AS IS" BASIS,
+     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+     See the License for the specific language governing permissions and
+     limitations under the License.
+-->
+<LinearLayout
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    android:layout_width="match_parent"
+    android:layout_height="wrap_content"
+    android:minHeight="?android:attr/listPreferredItemHeightSmall"
+    android:gravity="center_vertical"
+    android:paddingStart="?android:attr/listPreferredItemPaddingStart"
+    android:paddingEnd="?android:attr/listPreferredItemPaddingEnd"
+    android:background="?android:attr/activatedBackgroundIndicator"
+    android:clipToPadding="false">
+
+    <LinearLayout
+        android:id="@android:id/icon_frame"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:layout_marginStart="-4dp"
+        android:gravity="start|center_vertical"
+        android:orientation="horizontal"
+        android:paddingEnd="12dp"
+        android:paddingTop="4dp"
+        android:paddingBottom="4dp"/>
+
+    <LinearLayout
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:layout_weight="1"
+        android:gravity="center_vertical"
+        android:orientation="vertical">
+
+        <TextView android:id="@android:id/title"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:maxLines="2"
+            android:textAppearance="?android:attr/textAppearanceListItem" />
+
+        <TextView android:id="@android:id/summary"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:layout_alignStart="@android:id/title"
+            android:textAppearance="?android:attr/textAppearanceListItemSecondary"
+            android:textColor="?android:attr/textColorSecondary"
+            android:maxLines="10" />
+
+    </LinearLayout>
+
+    <LinearLayout android:id="@android:id/widget_frame"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:gravity="end|center_vertical"
+        android:orientation="horizontal" />
+
+</LinearLayout>
diff --git a/core/res/res/layout/slice_title.xml b/core/res/res/layout/slice_title.xml
new file mode 100644
index 0000000..455d59f
--- /dev/null
+++ b/core/res/res/layout/slice_title.xml
@@ -0,0 +1,26 @@
+<?xml version="1.0" encoding="utf-8"?>
+
+<!-- Copyright (C) 2017 The Android Open Source Project
+
+     Licensed under the Apache License, Version 2.0 (the "License");
+     you may not use this file except in compliance with the License.
+     You may obtain a copy of the License at
+
+          http://www.apache.org/licenses/LICENSE-2.0
+
+     Unless required by applicable law or agreed to in writing, software
+     distributed under the License is distributed on an "AS IS" BASIS,
+     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+     See the License for the specific language governing permissions and
+     limitations under the License.
+-->
+
+<!-- LinearLayout -->
+<TextView
+        xmlns:android="http://schemas.android.com/apk/res/android"
+        android:textAppearance="?android:attr/textAppearanceMedium"
+        android:textColor="?android:attr/textColorPrimary"
+        android:gravity="center"
+        android:layout_height="wrap_content"
+        android:padding="4dp"
+        android:layout_width="match_parent" />
diff --git a/core/res/res/values/dimens.xml b/core/res/res/values/dimens.xml
index b3d0053..14069e7 100644
--- a/core/res/res/values/dimens.xml
+++ b/core/res/res/values/dimens.xml
@@ -617,4 +617,10 @@
     -->
     <dimen name="autofill_save_icon_max_size">300dp</dimen>
 
+    <!-- Size of a slice shortcut view -->
+    <dimen name="slice_shortcut_size">56dp</dimen>
+    <!-- Size of action icons in a slice -->
+    <dimen name="slice_icon_size">24dp</dimen>
+    <!-- Standard padding used in a slice view -->
+    <dimen name="slice_padding">16dp</dimen>
 </resources>
diff --git a/core/res/res/values/strings.xml b/core/res/res/values/strings.xml
index 085f8dd..5189e7f 100644
--- a/core/res/res/values/strings.xml
+++ b/core/res/res/values/strings.xml
@@ -4727,4 +4727,7 @@
 
     <!-- Popup window default title to be read by a screen reader-->
     <string name="popup_window_default_title">Popup Window</string>
+
+    <!-- Format string for indicating there is more content in a slice view -->
+    <string name="slice_more_content">+ <xliff:g id="number" example="5">%1$d</xliff:g></string>
 </resources>
diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml
index cc74f17..53d09d8 100644
--- a/core/res/res/values/symbols.xml
+++ b/core/res/res/values/symbols.xml
@@ -3100,4 +3100,19 @@
   <java-symbol type="integer" name="config_stableDeviceDisplayWidth" />
   <java-symbol type="integer" name="config_stableDeviceDisplayHeight" />
   <java-symbol type="bool" name="config_display_no_service_when_sim_unready" />
+
+  <java-symbol type="layout" name="slice_grid" />
+  <java-symbol type="layout" name="slice_message_local" />
+  <java-symbol type="layout" name="slice_message" />
+  <java-symbol type="layout" name="slice_title" />
+  <java-symbol type="layout" name="slice_secondary_text" />
+  <java-symbol type="layout" name="slice_remote_input" />
+  <java-symbol type="layout" name="slice_small_template" />
+  <java-symbol type="id" name="remote_input_progress" />
+  <java-symbol type="id" name="remote_input_send" />
+  <java-symbol type="id" name="remote_input" />
+  <java-symbol type="dimen" name="slice_shortcut_size" />
+  <java-symbol type="dimen" name="slice_icon_size" />
+  <java-symbol type="dimen" name="slice_padding" />
+  <java-symbol type="string" name="slice_more_content" />
 </resources>