GridRowView.java

/*
 * Copyright 2017 The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package androidx.slice.widget;

import static android.app.slice.Slice.EXTRA_RANGE_VALUE;
import static android.app.slice.Slice.HINT_LARGE;
import static android.app.slice.Slice.HINT_NO_TINT;
import static android.app.slice.Slice.HINT_TITLE;
import static android.app.slice.Slice.SUBTYPE_MILLIS;
import static android.app.slice.SliceItem.FORMAT_ACTION;
import static android.app.slice.SliceItem.FORMAT_IMAGE;
import static android.app.slice.SliceItem.FORMAT_LONG;
import static android.app.slice.SliceItem.FORMAT_SLICE;
import static android.app.slice.SliceItem.FORMAT_TEXT;
import static android.view.ViewGroup.LayoutParams.MATCH_PARENT;
import static android.view.ViewGroup.LayoutParams.WRAP_CONTENT;

import static androidx.slice.core.SliceHints.LARGE_IMAGE;
import static androidx.slice.core.SliceHints.RAW_IMAGE_LARGE;
import static androidx.slice.core.SliceHints.SUBTYPE_DATE_PICKER;
import static androidx.slice.core.SliceHints.SUBTYPE_TIME_PICKER;
import static androidx.slice.widget.EventInfo.ACTION_TYPE_DATE_PICK;
import static androidx.slice.widget.EventInfo.ACTION_TYPE_TIME_PICK;
import static androidx.slice.widget.EventInfo.ACTION_TYPE_TOGGLE;
import static androidx.slice.widget.EventInfo.ROW_TYPE_DATE_PICK;
import static androidx.slice.widget.EventInfo.ROW_TYPE_TIME_PICK;
import static androidx.slice.widget.EventInfo.ROW_TYPE_TOGGLE;
import static androidx.slice.widget.SliceView.MODE_SMALL;

import android.app.DatePickerDialog;
import android.app.PendingIntent;
import android.app.TimePickerDialog;
import android.content.Context;
import android.content.Intent;
import android.content.res.Resources;
import android.graphics.drawable.Drawable;
import android.os.Build;
import android.util.AttributeSet;
import android.util.Log;
import android.util.Pair;
import android.util.TypedValue;
import android.view.Gravity;
import android.view.LayoutInflater;
import android.view.MotionEvent;
import android.view.View;
import android.view.ViewGroup;
import android.view.ViewTreeObserver;
import android.widget.DatePicker;
import android.widget.FrameLayout;
import android.widget.ImageView;
import android.widget.ImageView.ScaleType;
import android.widget.LinearLayout;
import android.widget.TextView;
import android.widget.TimePicker;

import androidx.annotation.ColorInt;
import androidx.annotation.RequiresApi;
import androidx.annotation.RestrictTo;
import androidx.core.content.ContextCompat;
import androidx.slice.CornerDrawable;
import androidx.slice.SliceItem;
import androidx.slice.core.SliceActionImpl;
import androidx.slice.core.SliceHints;
import androidx.slice.core.SliceQuery;
import androidx.slice.view.R;

import java.util.ArrayList;
import java.util.Calendar;
import java.util.Date;
import java.util.Iterator;
import java.util.List;

/**
 * @hide
 */
@RestrictTo(RestrictTo.Scope.LIBRARY)
@RequiresApi(19)
public class GridRowView extends SliceChildView implements View.OnClickListener,
        View.OnTouchListener {

    private static final String TAG = "GridRowView";

    private static final int TITLE_TEXT_LAYOUT = R.layout.abc_slice_title;
    private static final int TEXT_LAYOUT = R.layout.abc_slice_secondary_text;

    // Max number of text items that can show in a cell
    private static final int MAX_CELL_TEXT = 2;
    // Max number of text items that can show in a cell if the mode is small
    private static final int MAX_CELL_TEXT_SMALL = 1;
    // Max number of images that can show in a cell
    private static final int MAX_CELL_IMAGES = 1;

    private int mRowIndex;
    private int mRowCount;

    private final int mLargeImageHeight;
    private final int mSmallImageSize;
    private final int mSmallImageMinWidth;
    private final int mIconSize;
    private final int mGutter;
    private final int mTextPadding;

    private GridContent mGridContent;
    private final LinearLayout mViewContainer;
    private final View mForeground;
    int mMaxCells = -1;
    private final int[] mLoc = new int[2];

    boolean mMaxCellUpdateScheduled;

    private int mHiddenItemCount;

    public GridRowView(Context context) {
        this(context, null);
    }

    public GridRowView(Context context, AttributeSet attrs) {
        super(context, attrs);
        final Resources res = getContext().getResources();
        mViewContainer = new LinearLayout(getContext());
        mViewContainer.setOrientation(LinearLayout.HORIZONTAL);
        addView(mViewContainer, new LayoutParams(MATCH_PARENT, MATCH_PARENT));
        mViewContainer.setGravity(Gravity.CENTER_VERTICAL);
        mIconSize = res.getDimensionPixelSize(R.dimen.abc_slice_icon_size);
        mSmallImageSize = res.getDimensionPixelSize(R.dimen.abc_slice_small_image_size);
        mLargeImageHeight = res.getDimensionPixelSize(R.dimen.abc_slice_grid_image_only_height);
        mSmallImageMinWidth = res.getDimensionPixelSize(R.dimen.abc_slice_grid_image_min_width);
        mGutter = res.getDimensionPixelSize(R.dimen.abc_slice_grid_gutter);
        mTextPadding = res.getDimensionPixelSize(R.dimen.abc_slice_grid_text_padding);

        mForeground = new View(getContext());
        addView(mForeground, new LayoutParams(MATCH_PARENT, MATCH_PARENT));
    }

    @Override
    public void setInsets(int l, int t, int r, int b) {
        super.setInsets(l, t, r, b);
        mViewContainer.setPadding(l, t + getExtraTopPadding(), r, b + getExtraBottomPadding());
    }

    private int getExtraTopPadding() {
        if (mGridContent != null && mGridContent.isAllImages()) {
            // Might need to add padding if in first or last position
            if (mRowIndex == 0) {
                return mSliceStyle != null ? mSliceStyle.getGridTopPadding() : 0;
            }
        }
        return 0;
    }

    private int getExtraBottomPadding() {
        if (mGridContent != null && mGridContent.isAllImages()) {
            if (mRowIndex == mRowCount - 1 || getMode() == MODE_SMALL) {
                return mSliceStyle != null ? mSliceStyle.getGridBottomPadding() : 0;
            }
        }
        return 0;
    }

    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        int height = mGridContent.getHeight(mSliceStyle, mViewPolicy)
                + mInsetTop + mInsetBottom;
        heightMeasureSpec = MeasureSpec.makeMeasureSpec(height, MeasureSpec.EXACTLY);
        mViewContainer.getLayoutParams().height = height;
        super.onMeasure(widthMeasureSpec, heightMeasureSpec);
    }

    @Override
    public void setTint(@ColorInt int tintColor) {
        super.setTint(tintColor);
        if (mGridContent != null) {
            // TODO -- could be smarter about this
            resetView();
            populateViews();
        }
    }

    /**
     * This is called when GridView is being used as a component in a larger template.
     */
    @Override
    public void setSliceItem(SliceContent slice, boolean isHeader, int rowIndex,
            int rowCount, SliceView.OnSliceActionListener observer) {
        resetView();
        setSliceActionListener(observer);
        mRowIndex = rowIndex;
        mRowCount = rowCount;
        mGridContent = (GridContent) slice;

        if (!scheduleMaxCellsUpdate()) {
            populateViews();
        }
        mViewContainer.setPadding(mInsetStart, mInsetTop + getExtraTopPadding(), mInsetEnd,
                mInsetBottom + getExtraBottomPadding());
    }

    /**
     * Schedules update to determine the max number of cells that can be shown in this grid.
     *
     * @return true if update was scheduled, false if it wasn't needed
     */
    private boolean scheduleMaxCellsUpdate() {
        if (mGridContent == null || !mGridContent.isValid()) {
            return true;
        }
        if (getWidth() == 0) {
            // Need to wait for layout pass so we know how much width we have for the grid items.
            mMaxCellUpdateScheduled = true;
            getViewTreeObserver().addOnPreDrawListener(mMaxCellsUpdater);
            return true;
        } else {
            mMaxCells = getMaxCells();
            return false;
        }
    }

    int getMaxCells() {
        if (mGridContent == null || !mGridContent.isValid() || getWidth() == 0) {
            return -1;
        }
        ArrayList<GridContent.CellContent> cells = mGridContent.getGridContent();
        if (cells.size() > 1) {
            int desiredCellWidth;
            switch (mGridContent.getLargestImageMode()) {
                case LARGE_IMAGE:
                    desiredCellWidth = mLargeImageHeight;
                    break;
                case RAW_IMAGE_LARGE:
                    desiredCellWidth = mGridContent.getFirstImageSize(getContext()).x;
                    break;
                default:
                    desiredCellWidth = mSmallImageMinWidth;
            }
            return getWidth() / (desiredCellWidth + mGutter);
        } else {
            return 1;
        }
    }

    void populateViews() {
        if (mGridContent == null || !mGridContent.isValid()) {
            resetView();
            return;
        }
        if (scheduleMaxCellsUpdate()) {
            return;
        }
        if (mGridContent.getLayoutDir() != -1) {
            setLayoutDirection(mGridContent.getLayoutDir());
        }
        if (mGridContent.getContentIntent() != null) {
            EventInfo info = new EventInfo(getMode(), EventInfo.ACTION_TYPE_CONTENT,
                    EventInfo.ROW_TYPE_GRID, mRowIndex);
            Pair<SliceItem, EventInfo> tagItem = new Pair<>(mGridContent.getContentIntent(), info);
            mViewContainer.setTag(tagItem);
            makeEntireGridClickable(true);
        }
        CharSequence contentDescr = mGridContent.getContentDescription();
        if (contentDescr != null) {
            mViewContainer.setContentDescription(contentDescr);
        }
        ArrayList<GridContent.CellContent> cells = mGridContent.getGridContent();
        if (mGridContent.getLargestImageMode() == LARGE_IMAGE
                || mGridContent.getLargestImageMode() == RAW_IMAGE_LARGE) {
            mViewContainer.setGravity(Gravity.TOP);
        } else {
            mViewContainer.setGravity(Gravity.CENTER_VERTICAL);
        }
        int maxCells = mMaxCells;
        boolean hasSeeMore = mGridContent.getSeeMoreItem() != null;
        mHiddenItemCount = 0;
        for (int i = 0; i < cells.size(); i++) {
            if (mViewContainer.getChildCount() >= maxCells) {
                mHiddenItemCount = cells.size() - maxCells;
                if (hasSeeMore) {
                    addSeeMoreCount(mHiddenItemCount);
                }
                break;
            }
            addCell(cells.get(i), i, Math.min(cells.size(), maxCells));
        }
    }

    private void addSeeMoreCount(int numExtra) {
        // Remove last element
        View last = mViewContainer.getChildAt(mViewContainer.getChildCount() - 1);
        mViewContainer.removeView(last);

        SliceItem seeMoreItem = mGridContent.getSeeMoreItem();
        int index = mViewContainer.getChildCount();
        int total = mMaxCells;
        if ((FORMAT_SLICE.equals(seeMoreItem.getFormat())
                || FORMAT_ACTION.equals(seeMoreItem.getFormat()))
                && seeMoreItem.getSlice().getItems().size() > 0) {
            // It's a custom see more cell, add it
            addCell(new GridContent.CellContent(seeMoreItem), index, total);
            return;
        }

        // Default see more, create it
        LayoutInflater inflater = LayoutInflater.from(getContext());
        TextView extraText;
        View extraTint;
        ViewGroup seeMoreView;
        if (mGridContent.isAllImages()) {
            seeMoreView = (FrameLayout) inflater.inflate(R.layout.abc_slice_grid_see_more_overlay,
                    mViewContainer, false);
            seeMoreView.addView(last, 0, new LayoutParams(MATCH_PARENT, MATCH_PARENT));
            extraText = seeMoreView.findViewById(R.id.text_see_more_count);
            extraTint = seeMoreView.findViewById(R.id.overlay_see_more);
            extraTint.setBackground(new CornerDrawable(SliceViewUtil.getDrawable(
                    getContext(), android.R.attr.colorForeground),
                    mSliceStyle.getImageCornerRadius()));
        } else {
            seeMoreView = (LinearLayout) inflater.inflate(
                    R.layout.abc_slice_grid_see_more, mViewContainer, false);
            extraText = seeMoreView.findViewById(R.id.text_see_more_count);

            // Update text appearance
            TextView moreText = seeMoreView.findViewById(R.id.text_see_more);
            if (mSliceStyle != null && mRowStyle != null) {
                moreText.setTextSize(TypedValue.COMPLEX_UNIT_PX, mSliceStyle.getGridTitleSize());
                moreText.setTextColor(mRowStyle.getTitleColor());
            }
        }
        mViewContainer.addView(seeMoreView, new LinearLayout.LayoutParams(0, MATCH_PARENT, 1));
        extraText.setText(getResources().getString(R.string.abc_slice_more_content, numExtra));

        // Make it clickable
        EventInfo info = new EventInfo(getMode(), EventInfo.ACTION_TYPE_SEE_MORE,
                EventInfo.ROW_TYPE_GRID, mRowIndex);
        info.setPosition(EventInfo.POSITION_CELL, index, total);
        Pair<SliceItem, EventInfo> tagItem = new Pair<>(seeMoreItem, info);
        seeMoreView.setTag(tagItem);
        makeClickable(seeMoreView, true);
    }

    /**
     * Adds a cell to the grid view based on the provided {@link SliceItem}.
     */
    private void addCell(GridContent.CellContent cell, int index, int total) {
        final int maxCellText = getMode() == MODE_SMALL && mGridContent.hasImage()
                ? MAX_CELL_TEXT_SMALL : MAX_CELL_TEXT;
        LinearLayout cellContainer = new LinearLayout(getContext());
        cellContainer.setOrientation(LinearLayout.VERTICAL);
        cellContainer.setGravity(Gravity.CENTER_HORIZONTAL);

        ArrayList<SliceItem> cellItems = cell.getCellItems();
        SliceItem contentIntentItem = cell.getContentIntent();
        SliceItem pickerItem = cell.getPicker();
        SliceItem toggleItem = cell.getToggleItem();

        int textCount = 0;
        int imageCount = 0;
        boolean added = false;
        boolean isSingleItem = cellItems.size() == 1;
        List<SliceItem> textItems = null;
        // In small format we display one text item and prefer titles
        if (!isSingleItem && getMode() == MODE_SMALL) {
            // Get all our text items
            textItems = new ArrayList<>();
            for (SliceItem cellItem : cellItems) {
                if (FORMAT_TEXT.equals(cellItem.getFormat())) {
                    textItems.add(cellItem);
                }
            }
            // If we have more than 1 remove non-titles
            Iterator<SliceItem> iterator = textItems.iterator();
            while (textItems.size() > maxCellText) {
                SliceItem item = iterator.next();
                if (!item.hasAnyHints(HINT_TITLE, HINT_LARGE)) {
                    iterator.remove();
                }
            }
        }
        SliceItem prevItem = null;
        for (int i = 0; i < cellItems.size(); i++) {
            SliceItem item = cellItems.get(i);
            final String itemFormat = item.getFormat();
            int padding = determinePadding(prevItem);
            if (textCount < maxCellText && (FORMAT_TEXT.equals(itemFormat)
                    || FORMAT_LONG.equals(itemFormat))) {
                if (textItems != null && !textItems.contains(item)) {
                    continue;
                }
                if (addTextItem(item, cellContainer, padding)) {
                    prevItem = item;
                    textCount++;
                    added = true;
                }
            } else if (imageCount < MAX_CELL_IMAGES && FORMAT_IMAGE.equals(item.getFormat())) {
                if (addImageItem(item, cell.getOverlayItem(), mTintColor, cellContainer,
                        isSingleItem)) {
                    prevItem = item;
                    imageCount++;
                    added = true;
                }
            }
        }
        if (pickerItem != null) {
            if (SUBTYPE_DATE_PICKER.equals(pickerItem.getSubType())) {
                added = addPickerItem(pickerItem, cellContainer, determinePadding(prevItem),
                        /*isDatePicker=*/ true);
            } else if (SUBTYPE_TIME_PICKER.equals(pickerItem.getSubType())) {
                added = addPickerItem(pickerItem, cellContainer, determinePadding(prevItem),
                        /*isDatePicker=*/ false);
            }
        }
        SliceActionView sav = null;
        if (toggleItem != null) {
            sav = new SliceActionView(getContext(), mSliceStyle, mRowStyle);
            cellContainer.addView(sav);
            added = true;
        }
        if (added) {
            CharSequence contentDescr = cell.getContentDescription();
            if (contentDescr != null) {
                cellContainer.setContentDescription(contentDescr);
            }
            mViewContainer.addView(cellContainer,
                    new LinearLayout.LayoutParams(0, WRAP_CONTENT, 1));
            if (index != total - 1) {
                // If we're not the last or only element add space between items
                MarginLayoutParams lp =
                        (LinearLayout.MarginLayoutParams) cellContainer.getLayoutParams();
                lp.setMarginEnd(mGutter);
                cellContainer.setLayoutParams(lp);
            }
            if (contentIntentItem != null) {
                EventInfo info = new EventInfo(getMode(), EventInfo.ACTION_TYPE_BUTTON,
                        EventInfo.ROW_TYPE_GRID, mRowIndex);
                info.setPosition(EventInfo.POSITION_CELL, index, total);
                Pair<SliceItem, EventInfo> tagItem = new Pair<>(contentIntentItem, info);
                cellContainer.setTag(tagItem);
                makeClickable(cellContainer, true);
            }
            if (toggleItem != null) {
                EventInfo info =
                        new EventInfo(getMode(), ACTION_TYPE_TOGGLE, ROW_TYPE_TOGGLE, mRowIndex);
                sav.setAction(
                        new SliceActionImpl(toggleItem),
                        info, mObserver, mTintColor, mLoadingListener);
                info.setPosition(EventInfo.POSITION_CELL, index, total);
            }
        }
    }

    /**
     * Adds simple text based items to a container. Simple text items include text and
     * timestamps.
     *
     * @param item      item to add to the container.
     * @param container the container to add to.
     * @param padding   the padding to apply to the item.
     * @return Whether an item was added.
     */
    private boolean addTextItem(SliceItem item, ViewGroup container, int padding) {
        final String format = item.getFormat();
        if (!FORMAT_TEXT.equals(format) && !FORMAT_LONG.equals(format)) {
            return false;
        }
        boolean isTitle = SliceQuery.hasAnyHints(item, HINT_LARGE, HINT_TITLE);
        TextView tv = (TextView) LayoutInflater.from(getContext()).inflate(isTitle
                ? TITLE_TEXT_LAYOUT : TEXT_LAYOUT, null);
        if (mSliceStyle != null && mRowStyle != null) {
            tv.setTextSize(TypedValue.COMPLEX_UNIT_PX, isTitle
                    ? mSliceStyle.getGridTitleSize() : mSliceStyle.getGridSubtitleSize());
            tv.setTextColor(isTitle
                    ? mRowStyle.getTitleColor() : mRowStyle.getSubtitleColor());
        }
        CharSequence text = FORMAT_LONG.equals(format)
                ? SliceViewUtil.getTimestampString(getContext(), item.getLong())
                : item.getSanitizedText();
        tv.setText(text);
        container.addView(tv);
        tv.setPadding(0, padding, 0, 0);
        return true;
    }

    /**
     * Adds simple image based item to a container.
     *
     * @param item        item to add to the container.
     * @param overlayItem overlaid text to add to the image.
     * @param container   the container to add to.
     * @param isSingle    whether this is the only item in the cell or not.
     * @return Whether an item was added.
     */
    private boolean addImageItem(SliceItem item, SliceItem overlayItem, int color,
            ViewGroup container, boolean isSingle) {
        final String format = item.getFormat();
        final boolean hasRoundedImage =
                mSliceStyle != null && mSliceStyle.getApplyCornerRadiusToLargeImages();
        if (!FORMAT_IMAGE.equals(format) || item.getIcon() == null) {
            return false;
        }
        Drawable d = item.getIcon().loadDrawable(getContext());
        if (d == null) {
            return false;
        }
        ImageView iv = new ImageView(getContext());
        if (hasRoundedImage) {
            CornerDrawable cd = new CornerDrawable(d, mSliceStyle.getImageCornerRadius());
            iv.setImageDrawable(cd);
        } else {
            iv.setImageDrawable(d);
        }
        LinearLayout.LayoutParams lp;
        if (item.hasHint(SliceHints.HINT_RAW)) {
            iv.setScaleType(ScaleType.CENTER_INSIDE);
            lp = new LinearLayout.LayoutParams(mGridContent.getFirstImageSize(getContext()).x,
                    mGridContent.getFirstImageSize(getContext()).y);
        } else if (item.hasHint(HINT_LARGE)) {
            iv.setScaleType(hasRoundedImage ? ScaleType.FIT_XY : ScaleType.CENTER_CROP);
            int height = isSingle ? MATCH_PARENT : mLargeImageHeight;
            lp = new LinearLayout.LayoutParams(MATCH_PARENT, height);
        } else {
            boolean isIcon = !item.hasHint(HINT_NO_TINT);
            int size = !isIcon ? mSmallImageSize : mIconSize;
            iv.setScaleType(isIcon ? ScaleType.CENTER_INSIDE : ScaleType.CENTER_CROP);
            lp = new LinearLayout.LayoutParams(size, size);
        }
        if (color != -1 && !item.hasHint(HINT_NO_TINT)) {
            iv.setColorFilter(color);
        }
        // don't add an overlay on see more
        if (overlayItem == null || mViewContainer.getChildCount() == mMaxCells - 1) {
            container.addView(iv, lp);
            return true;
        }
        // add overlay on top of the ImageView
        LayoutInflater inflater = LayoutInflater.from(getContext());
        TextView overlayText;
        View overlayTint;
        ViewGroup overlayView;
        overlayView = (FrameLayout) inflater.inflate(R.layout.abc_slice_grid_text_overlay_image,
                container, false);
        overlayView.addView(iv, 0, new LayoutParams(WRAP_CONTENT, WRAP_CONTENT));
        overlayText = overlayView.findViewById(R.id.text_overlay);
        overlayText.setText(overlayItem.getText());
        overlayTint = overlayView.findViewById(R.id.tint_overlay);
        overlayTint.setBackground(new CornerDrawable(ContextCompat.getDrawable(getContext(),
                R.drawable.abc_slice_gradient), mSliceStyle.getImageCornerRadius()));
        container.addView(overlayView, lp);
        return true;
    }

    /**
     * Adds date or time picker to a container.
     *
     * @param pickerItem item to add to the container.
     * @param container      the container to add to.
     * @param padding        the padding to apply to the item.
     * @param isDatePicker   if true, it is a date picker, otherwise is a time picker.
     * @return Whether an item was added.
     */
    private boolean addPickerItem(SliceItem pickerItem, ViewGroup container, int padding,
            boolean isDatePicker) {
        SliceItem dateTimeItem = SliceQuery.findSubtype(pickerItem, FORMAT_LONG,
                SUBTYPE_MILLIS);
        if (dateTimeItem == null) {
            return false;
        }
        long dateTimeMillis = dateTimeItem.getLong();

        TextView tv = (TextView) LayoutInflater.from(getContext()).inflate(TITLE_TEXT_LAYOUT, null);
        if (mSliceStyle != null) {
            tv.setTextSize(TypedValue.COMPLEX_UNIT_PX, mSliceStyle.getGridTitleSize());
            tv.setTextColor(mSliceStyle.getTitleColor());
        }

        Date date = new Date(dateTimeMillis);
        SliceItem titleItem = SliceQuery.find(pickerItem, FORMAT_TEXT, HINT_TITLE,
                /*nonHints=*/null);
        if (titleItem != null) {
            tv.setText(titleItem.getText());
        }

        int rowIndex = mRowIndex;

        container.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {
                Calendar cal = Calendar.getInstance();
                cal.setTime(date);
                if (isDatePicker) {
                    DatePickerDialog dialog = new DatePickerDialog(
                            getContext(),
                            R.style.DialogTheme,
                            new DateSetListener(pickerItem, rowIndex),
                            cal.get(Calendar.YEAR),
                            cal.get(Calendar.MONTH),
                            cal.get(Calendar.DAY_OF_MONTH));
                    dialog.show();
                } else {
                    TimePickerDialog dialog = new TimePickerDialog(
                            getContext(),
                            R.style.DialogTheme,
                            new TimeSetListener(pickerItem, rowIndex),
                            cal.get(Calendar.HOUR_OF_DAY),
                            cal.get(Calendar.MINUTE),
                            false);
                    dialog.show();
                }
            }
        });
        container.setClickable(true);

        int backgroundAttr = android.R.attr.selectableItemBackground;
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
            backgroundAttr = android.R.attr.selectableItemBackgroundBorderless;
        }
        container.setBackground(SliceViewUtil.getDrawable(getContext(), backgroundAttr));
        container.addView(tv);
        tv.setPadding(0, padding, 0, 0);
        return true;
    }

    private class DateSetListener implements DatePickerDialog.OnDateSetListener {
        private final SliceItem mActionItem;
        private final int mRowIndex;

        DateSetListener(SliceItem datePickerItem, int mRowIndex) {
            this.mActionItem = datePickerItem;
            this.mRowIndex = mRowIndex;
        }

        @Override
        public void onDateSet(DatePicker datePicker, int year, int month, int day) {
            Calendar c = Calendar.getInstance();
            c.set(year, month, day);
            Date date = c.getTime();


            if (mActionItem != null) {
                try {
                    mActionItem.fireAction(getContext(),
                            new Intent().addFlags(Intent.FLAG_RECEIVER_FOREGROUND)
                                    .putExtra(EXTRA_RANGE_VALUE, date.getTime()));
                    if (mObserver != null) {
                        EventInfo info = new EventInfo(getMode(), ACTION_TYPE_DATE_PICK,
                                ROW_TYPE_DATE_PICK,
                                mRowIndex);
                        mObserver.onSliceAction(info, mActionItem);
                    }
                } catch (PendingIntent.CanceledException e) {
                    Log.e(TAG, "PendingIntent for slice cannot be sent", e);
                }
            }
        }
    }

    private class TimeSetListener implements TimePickerDialog.OnTimeSetListener {
        private final SliceItem mActionItem;
        private final int mRowIndex;

        TimeSetListener(SliceItem timePickerItem, int mRowIndex) {
            this.mActionItem = timePickerItem;
            this.mRowIndex = mRowIndex;
        }

        @Override
        public void onTimeSet(TimePicker timePicker, int hour, int minute) {
            Calendar c = Calendar.getInstance();
            Date time = c.getTime();
            time.setHours(hour);
            time.setMinutes(minute);

            if (mActionItem != null) {
                try {
                    mActionItem.fireAction(getContext(),
                            new Intent().addFlags(Intent.FLAG_RECEIVER_FOREGROUND)
                                    .putExtra(EXTRA_RANGE_VALUE, time.getTime()));
                    if (mObserver != null) {
                        EventInfo info = new EventInfo(getMode(), ACTION_TYPE_TIME_PICK,
                                ROW_TYPE_TIME_PICK,
                                mRowIndex);
                        mObserver.onSliceAction(info, mActionItem);
                    }
                } catch (PendingIntent.CanceledException e) {
                    Log.e(TAG, "PendingIntent for slice cannot be sent", e);
                }
            }
        }
    }

    private int determinePadding(SliceItem prevItem) {
        if (prevItem == null) {
            // No need for top padding
            return 0;
        } else if (FORMAT_IMAGE.equals(prevItem.getFormat())) {
            return mTextPadding;
        } else if (FORMAT_TEXT.equals(prevItem.getFormat())
                || FORMAT_LONG.equals(prevItem.getFormat())) {
            return mSliceStyle != null ? mSliceStyle.getVerticalGridTextPadding() : 0;
        }
        return 0;
    }

    private void makeEntireGridClickable(boolean isClickable) {
        mViewContainer.setOnTouchListener(isClickable ? this : null);
        mViewContainer.setOnClickListener((isClickable ? this : null));
        mForeground.setBackground(isClickable
                ? SliceViewUtil.getDrawable(
                        getContext(), android.R.attr.selectableItemBackground)
                : null);
        mViewContainer.setClickable(isClickable);
    }

    private void makeClickable(View layout, boolean isClickable) {
        layout.setOnClickListener(isClickable ? this : null);
        int backgroundAttr = android.R.attr.selectableItemBackground;
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
            backgroundAttr = android.R.attr.selectableItemBackgroundBorderless;
        }
        layout.setBackground(isClickable
                ? SliceViewUtil.getDrawable(getContext(), backgroundAttr)
                : null);
        layout.setClickable(isClickable);
    }

    @Override
    @SuppressWarnings("unchecked")
    public void onClick(View view) {
        Pair<SliceItem, EventInfo> tagItem = (Pair<SliceItem, EventInfo>) view.getTag();
        final SliceItem sliceItem = tagItem.first;
        final EventInfo info = tagItem.second;
        if (sliceItem != null) {
            final SliceItem actionItem = SliceQuery.find(sliceItem,
                    FORMAT_ACTION, (String) null, null);
            if (actionItem != null) {
                try {
                    actionItem.fireAction(null, null);
                    if (mObserver != null) {
                        mObserver.onSliceAction(info, actionItem);
                    }
                } catch (PendingIntent.CanceledException e) {
                    Log.e(TAG, "PendingIntent for slice cannot be sent", e);
                }
            }
        }
    }

    @Override
    public boolean onTouch(View view, MotionEvent event) {
        onForegroundActivated(event);
        return false;
    }

    private void onForegroundActivated(MotionEvent event) {
        if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.LOLLIPOP) {
            mForeground.getLocationOnScreen(mLoc);
            final int x = (int) (event.getRawX() - mLoc[0]);
            final int y = (int) (event.getRawY() - mLoc[1]);
            mForeground.getBackground().setHotspot(x, y);
        }
        int action = event.getActionMasked();
        if (action == android.view.MotionEvent.ACTION_DOWN) {
            mForeground.setPressed(true);
        } else if (action == android.view.MotionEvent.ACTION_CANCEL
                || action == android.view.MotionEvent.ACTION_UP
                || action == android.view.MotionEvent.ACTION_MOVE) {
            mForeground.setPressed(false);
        }
    }

    @Override
    public void resetView() {
        if (mMaxCellUpdateScheduled) {
            mMaxCellUpdateScheduled = false;
            getViewTreeObserver().removeOnPreDrawListener(mMaxCellsUpdater);
        }
        mViewContainer.removeAllViews();
        setLayoutDirection(View.LAYOUT_DIRECTION_INHERIT);
        makeEntireGridClickable(false);
    }

    @Override
    public int getHiddenItemCount() {
        return mHiddenItemCount;
    }

    private final ViewTreeObserver.OnPreDrawListener mMaxCellsUpdater =
            new ViewTreeObserver.OnPreDrawListener() {
                @Override
                public boolean onPreDraw() {
                    mMaxCells = getMaxCells();
                    populateViews();
                    getViewTreeObserver().removeOnPreDrawListener(this);
                    mMaxCellUpdateScheduled = false;
                    return true;
                }
            };
}