Artifact androidx.slice:slice-view:1.1.0-alpha02 it located at Google repository (https://maven.google.com/)
Row item is in small template format and can be used to construct list items for use
with TemplateView.
Sets the style information for this view.
Sets the insets (padding) for the slice.
Sets a custom color to use for tinting elements like icons for this view.
Sets whether the last updated time should be displayed.
Sets whether this slice can have 2 lines of subtitle text in the first row.
This is called when RowView is being used as a component in a large template.
Sets when the content of this view was last updated.
Sets the actions that are being loaded.
Called when the view should be reset.
/*
* 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_PARTIAL;
import static android.app.slice.Slice.HINT_SHORTCUT;
import static android.app.slice.Slice.SUBTYPE_MAX;
import static android.app.slice.Slice.SUBTYPE_MILLIS;
import static android.app.slice.Slice.SUBTYPE_TOGGLE;
import static android.app.slice.Slice.SUBTYPE_VALUE;
import static android.app.slice.SliceItem.FORMAT_ACTION;
import static android.app.slice.SliceItem.FORMAT_IMAGE;
import static android.app.slice.SliceItem.FORMAT_INT;
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 androidx.slice.Slice.EXTRA_SELECTION;
import static androidx.slice.Slice.SUBTYPE_RANGE_MODE;
import static androidx.slice.core.SliceHints.HINT_RAW;
import static androidx.slice.core.SliceHints.HINT_SELECTION_OPTION;
import static androidx.slice.core.SliceHints.INDETERMINATE_RANGE;
import static androidx.slice.core.SliceHints.STAR_RATING;
import static androidx.slice.core.SliceHints.SUBTYPE_DATE_PICKER;
import static androidx.slice.core.SliceHints.SUBTYPE_MIN;
import static androidx.slice.core.SliceHints.SUBTYPE_SELECTION_OPTION_KEY;
import static androidx.slice.core.SliceHints.SUBTYPE_SELECTION_OPTION_VALUE;
import static androidx.slice.core.SliceHints.SUBTYPE_TIME_PICKER;
import static androidx.slice.widget.EventInfo.ACTION_TYPE_BUTTON;
import static androidx.slice.widget.EventInfo.ACTION_TYPE_DATE_PICK;
import static androidx.slice.widget.EventInfo.ACTION_TYPE_SELECTION;
import static androidx.slice.widget.EventInfo.ACTION_TYPE_SLIDER;
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_LIST;
import static androidx.slice.widget.EventInfo.ROW_TYPE_SELECTION;
import static androidx.slice.widget.EventInfo.ROW_TYPE_SLIDER;
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.PendingIntent.CanceledException;
import android.app.TimePickerDialog;
import android.content.Context;
import android.content.Intent;
import android.graphics.PorterDuff;
import android.graphics.Typeface;
import android.graphics.drawable.Drawable;
import android.graphics.drawable.LayerDrawable;
import android.os.Build;
import android.os.Handler;
import android.text.SpannableString;
import android.text.TextUtils;
import android.text.format.DateUtils;
import android.text.style.StyleSpan;
import android.util.ArrayMap;
import android.util.Log;
import android.util.TypedValue;
import android.view.Gravity;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.AdapterView;
import android.widget.ArrayAdapter;
import android.widget.Button;
import android.widget.DatePicker;
import android.widget.ImageView;
import android.widget.LinearLayout;
import android.widget.ProgressBar;
import android.widget.RatingBar;
import android.widget.SeekBar;
import android.widget.Spinner;
import android.widget.TextView;
import android.widget.TimePicker;
import androidx.annotation.ColorInt;
import androidx.annotation.NonNull;
import androidx.annotation.RequiresApi;
import androidx.annotation.RestrictTo;
import androidx.core.graphics.drawable.DrawableCompat;
import androidx.core.graphics.drawable.IconCompat;
import androidx.core.view.ViewCompat;
import androidx.slice.CornerDrawable;
import androidx.slice.SliceItem;
import androidx.slice.SliceStructure;
import androidx.slice.core.SliceAction;
import androidx.slice.core.SliceActionImpl;
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.HashSet;
import java.util.List;
import java.util.Set;
/**
* Row item is in small template format and can be used to construct list items for use
* with {@link TemplateView}.
*
* @hide
*/
@RestrictTo(RestrictTo.Scope.LIBRARY)
@RequiresApi(19)
public class RowView extends SliceChildView implements View.OnClickListener,
AdapterView.OnItemSelectedListener {
private static final String TAG = "RowView";
private static final int HEIGHT_UNBOUND = -1;
// The number of items that fit on the right hand side of a small slice
// TODO: this should be based on available width
private static final int MAX_END_ITEMS = 3;
// How frequently (ms) intent can be sent in response to slider moving.
private static final int SLIDER_INTERVAL = 200;
// The index for star rating's layer drawable's foreground
private static final int STAR_COLOR_INDEX = 2;
// On versions before M, SeekBar won't render properly if stretched taller than the default
// size.
private static final boolean sCanSpecifyLargerRangeBarHeight =
Build.VERSION.SDK_INT >= Build.VERSION_CODES.M;
private final LinearLayout mRootView;
private final LinearLayout mStartContainer;
private final LinearLayout mContent;
private final LinearLayout mSubContent;
private final TextView mPrimaryText;
private final TextView mSecondaryText;
private final TextView mLastUpdatedText;
private final View mBottomDivider;
private final View mActionDivider;
private final ArrayMap<SliceActionImpl, SliceActionView> mToggles = new ArrayMap<>();
private final ArrayMap<SliceActionImpl, SliceActionView> mActions = new ArrayMap<>();
private final LinearLayout mEndContainer;
private View mSeeMoreView;
private View mRangeBar;
private boolean mIsStarRating;
private final ProgressBar mActionSpinner;
protected Set<SliceItem> mLoadingActions = new HashSet<>();
@SuppressWarnings("WeakerAccess") /* synthetic access */
boolean mShowActionSpinner;
private Spinner mSelectionSpinner;
@SuppressWarnings("WeakerAccess") /* synthetic access */
int mRowIndex;
@SuppressWarnings("WeakerAccess") /* synthetic access */
RowContent mRowContent;
private SliceActionImpl mRowAction;
private SliceItem mStartItem;
private boolean mIsHeader;
private List<SliceAction> mHeaderActions;
// Indicates whether header rows can have 2 lines of subtitle text
private boolean mAllowTwoLines;
// Indicates if there's a slider in this row that is currently being interacted with.
@SuppressWarnings("WeakerAccess") /* synthetic access */
boolean mIsRangeSliding;
// Indicates that there was an update to the row but we skipped it while the slice was
// being interacted with.
@SuppressWarnings("WeakerAccess") /* synthetic access */
boolean mRangeHasPendingUpdate;
@SuppressWarnings("WeakerAccess") /* synthetic access */
boolean mRangeUpdaterRunning;
@SuppressWarnings("WeakerAccess") /* synthetic access */
Handler mHandler;
@SuppressWarnings("WeakerAccess") /* synthetic access */
long mLastSentRangeUpdate;
// TODO: mRangeValue is in 0..(mRangeMaxValue-mRangeMinValue) at initialization, and in
// mRangeMinValue..mRangeMaxValue after user interaction. As far as I know, this doesn't
// cause any incorrect behavior, but it is confusing and error-prone.
@SuppressWarnings("WeakerAccess") /* synthetic access */
int mRangeValue;
@SuppressWarnings("WeakerAccess") /* synthetic access */
int mRangeMinValue;
@SuppressWarnings("WeakerAccess") /* synthetic access */
int mRangeMaxValue;
private SliceItem mRangeItem;
private SliceItem mSelectionItem;
private ArrayList<String> mSelectionOptionKeys;
private ArrayList<CharSequence> mSelectionOptionValues;
private int mImageSize;
private int mIconSize;
// How big mRangeBar wants to be.
private int mMeasuredRangeHeight;
public RowView(Context context) {
super(context);
mIconSize = getContext().getResources().getDimensionPixelSize(R.dimen.abc_slice_icon_size);
mImageSize = getContext().getResources().getDimensionPixelSize(
R.dimen.abc_slice_small_image_size);
mRootView = (LinearLayout) LayoutInflater.from(context).inflate(
R.layout.abc_slice_small_template, this, false);
addView(mRootView);
mStartContainer = findViewById(R.id.icon_frame);
mContent = findViewById(android.R.id.content);
mSubContent = findViewById(R.id.subcontent);
mPrimaryText = findViewById(android.R.id.title);
mSecondaryText = findViewById(android.R.id.summary);
mLastUpdatedText = findViewById(R.id.last_updated);
mBottomDivider = findViewById(R.id.bottom_divider);
mActionDivider = findViewById(R.id.action_divider);
mActionSpinner = findViewById(R.id.action_sent_indicator);
SliceViewUtil.tintIndeterminateProgressBar(getContext(), mActionSpinner);
mEndContainer = findViewById(android.R.id.widget_frame);
ViewCompat.setImportantForAccessibility(this, ViewCompat.IMPORTANT_FOR_ACCESSIBILITY_NO);
ViewCompat.setImportantForAccessibility(
mContent, ViewCompat.IMPORTANT_FOR_ACCESSIBILITY_NO);
}
@Override
public void setStyle(SliceStyle styles, RowStyle rowStyle) {
super.setStyle(styles, rowStyle);
applyRowStyle();
}
private void applyRowStyle() {
if (mSliceStyle == null || mRowStyle == null) {
return;
}
setViewSidePaddings(mStartContainer,
mRowStyle.getTitleItemStartPadding(), mRowStyle.getTitleItemEndPadding());
setViewSidePaddings(mContent,
mRowStyle.getContentStartPadding(), mRowStyle.getContentEndPadding());
setViewSidePaddings(mPrimaryText,
mRowStyle.getTitleStartPadding(), mRowStyle.getTitleEndPadding());
setViewSidePaddings(mSubContent,
mRowStyle.getSubContentStartPadding(), mRowStyle.getSubContentEndPadding());
setViewSidePaddings(mEndContainer,
mRowStyle.getEndItemStartPadding(), mRowStyle.getEndItemEndPadding());
setViewSideMargins(mBottomDivider,
mRowStyle.getBottomDividerStartPadding(), mRowStyle.getBottomDividerEndPadding());
setViewHeight(mActionDivider, mRowStyle.getActionDividerHeight());
if (mRowStyle.getTintColor() != -1) {
setTint(mRowStyle.getTintColor());
}
}
private void setViewSidePaddings(View v, int start, int end) {
final boolean isNoPaddingSet = start < 0 && end < 0;
if (v == null || isNoPaddingSet) {
return;
}
v.setPaddingRelative(
start >= 0 ? start : v.getPaddingStart(),
v.getPaddingTop(),
end >= 0 ? end : v.getPaddingEnd(),
v.getPaddingBottom());
}
private void setViewSideMargins(View v, int start, int end) {
final boolean isNoMarginSet = start < 0 && end < 0;
if (v == null || isNoMarginSet) {
return;
}
final MarginLayoutParams params = (MarginLayoutParams) v.getLayoutParams();
if (start >= 0) {
params.setMarginStart(start);
}
if (end >= 0) {
params.setMarginEnd(end);
}
v.setLayoutParams(params);
}
private void setViewHeight(View v, int height) {
if (v != null && height >= 0) {
final ViewGroup.LayoutParams params = v.getLayoutParams();
params.height = height;
v.setLayoutParams(params);
}
}
private void setViewWidth(View v, int width) {
if (v != null && width >= 0) {
final ViewGroup.LayoutParams params = v.getLayoutParams();
params.width = width;
v.setLayoutParams(params);
}
}
@Override
public void setInsets(int l, int t, int r, int b) {
super.setInsets(l, t, r, b);
setPadding(l, t, r, b);
}
/**
* @return height row content (i.e. title, subtitle) without the height of the range element.
*/
private int getRowContentHeight() {
int rowHeight = mRowContent.getHeight(mSliceStyle, mViewPolicy);
if (mRangeBar != null && mStartItem == null) {
rowHeight -= mSliceStyle.getRowRangeHeight();
}
if (mSelectionSpinner != null) {
rowHeight -= mSliceStyle.getRowSelectionHeight();
}
return rowHeight;
}
@Override
public void setTint(@ColorInt int tintColor) {
super.setTint(tintColor);
if (mRowContent != null) {
// TODO -- can be smarter about this
populateViews(true);
}
}
/**
* @param actions if the actions are null then there are no header actions for this row.
* If the actions are an empty list, then something has explicitly set that no header
* actions should appear.
*/
@Override
public void setSliceActions(List<SliceAction> actions) {
mHeaderActions = actions;
if (mRowContent != null) {
updateEndItems();
}
}
@Override
public void setShowLastUpdated(boolean showLastUpdated) {
super.setShowLastUpdated(showLastUpdated);
if (mRowContent != null) {
populateViews(true);
}
}
@Override
public void setAllowTwoLines(boolean allowTwoLines) {
mAllowTwoLines = allowTwoLines;
if (mRowContent != null) {
populateViews(true);
}
}
private void measureChildWithExactHeight(View child, int widthMeasureSpec, int childHeight) {
int heightMeasureSpec = MeasureSpec.makeMeasureSpec(childHeight + mInsetTop + mInsetBottom,
MeasureSpec.EXACTLY);
measureChild(child, widthMeasureSpec, heightMeasureSpec);
}
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
int childWidth = 0;
int rowHeight = getRowContentHeight();
if (rowHeight != 0) {
// Might be gone if we have range / progress but nothing else
mRootView.setVisibility(View.VISIBLE);
measureChildWithExactHeight(mRootView, widthMeasureSpec, rowHeight);
childWidth = mRootView.getMeasuredWidth();
} else {
mRootView.setVisibility(View.GONE);
}
if (mRangeBar != null && mStartItem == null) {
// If we're on a platform where SeekBar can't be stretched vertically, find out the
// exact size it would like to be so we can honor that in onLayout.
if (sCanSpecifyLargerRangeBarHeight) {
measureChildWithExactHeight(mRangeBar, widthMeasureSpec,
mSliceStyle.getRowRangeHeight());
} else {
measureChild(mRangeBar, widthMeasureSpec,
MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED));
}
// Remember the measured height later for onLayout, since super.onMeasure will overwrite
// it.
mMeasuredRangeHeight = mRangeBar.getMeasuredHeight();
childWidth = Math.max(childWidth, mRangeBar.getMeasuredWidth());
} else if (mSelectionSpinner != null) {
measureChildWithExactHeight(mSelectionSpinner, widthMeasureSpec,
mSliceStyle.getRowSelectionHeight());
childWidth = Math.max(childWidth, mSelectionSpinner.getMeasuredWidth());
}
childWidth = Math.max(childWidth + mInsetStart + mInsetEnd, getSuggestedMinimumWidth());
int rowContentHeight = mRowContent != null ? mRowContent.getHeight(mSliceStyle,
mViewPolicy) : 0;
setMeasuredDimension(resolveSizeAndState(childWidth, widthMeasureSpec, 0),
rowContentHeight + mInsetTop + mInsetBottom);
}
@Override
protected void onLayout(boolean changed, int l, int t, int r, int b) {
int leftPadding = getPaddingLeft();
mRootView.layout(leftPadding, mInsetTop, mRootView.getMeasuredWidth() + leftPadding,
getRowContentHeight() + mInsetTop);
if (mRangeBar != null && mStartItem == null) {
// If we're on a platform where SeekBar can't be stretched vertically, then
// mMeasuredRangeHeight can (and probably will) be smaller than the ideal height, so we
// need to add some padding to make mRangeBar look like it's the larger size.
int verticalPadding = (mSliceStyle.getRowRangeHeight() - mMeasuredRangeHeight) / 2;
int top = (getRowContentHeight() + verticalPadding + mInsetTop);
int bottom = top + mMeasuredRangeHeight;
mRangeBar.layout(leftPadding, top, mRangeBar.getMeasuredWidth() + leftPadding, bottom);
} else if (mSelectionSpinner != null) {
int top = getRowContentHeight() + mInsetTop;
int bottom = top + mSelectionSpinner.getMeasuredHeight();
mSelectionSpinner.layout(leftPadding, top,
mSelectionSpinner.getMeasuredWidth() + leftPadding, bottom);
}
}
/**
* This is called when RowView is being used as a component in a large template.
*/
@Override
public void setSliceItem(SliceContent content, boolean isHeader, int index,
int rowCount, SliceView.OnSliceActionListener observer) {
setSliceActionListener(observer);
boolean isUpdate = false;
if (content != null && mRowContent != null && mRowContent.isValid()) {
// Might be same slice
SliceStructure prevSs = mRowContent != null
? new SliceStructure(mRowContent.getSliceItem()) : null;
SliceStructure newSs = new SliceStructure(content.getSliceItem().getSlice());
boolean sameStructure = prevSs != null && prevSs.equals(newSs);
boolean sameSliceId = prevSs != null
&& prevSs.getUri() != null && prevSs.getUri().equals(newSs.getUri());
isUpdate = sameSliceId && sameStructure;
}
mShowActionSpinner = false;
mIsHeader = isHeader;
mRowContent = (RowContent) content;
mRowIndex = index;
populateViews(isUpdate);
}
private void populateViews(boolean isUpdate) {
boolean skipSliderUpdate = isUpdate && mIsRangeSliding;
if (!skipSliderUpdate) {
resetViewState();
}
if (mRowContent.getLayoutDir() != -1) {
setLayoutDirection(mRowContent.getLayoutDir());
}
if (mRowContent.isDefaultSeeMore()) {
showSeeMore();
return;
}
CharSequence contentDescr = mRowContent.getContentDescription();
if (contentDescr != null) {
mContent.setContentDescription(contentDescr);
}
mStartItem = mRowContent.getStartItem();
boolean showStart = mStartItem != null && (!mRowContent.getIsHeader()
|| mRowContent.hasTitleItems());
if (showStart) {
showStart = addItem(mStartItem, mTintColor, true /* isStart */);
}
mStartContainer.setVisibility(showStart ? View.VISIBLE : View.GONE);
final SliceItem titleItem = mRowContent.getTitleItem();
if (titleItem != null) {
mPrimaryText.setText(titleItem.getSanitizedText());
}
if (mSliceStyle != null) {
mPrimaryText.setTextSize(TypedValue.COMPLEX_UNIT_PX, mIsHeader
? mSliceStyle.getHeaderTitleSize()
: mSliceStyle.getTitleSize());
mPrimaryText.setTextColor(mRowStyle.getTitleColor());
}
mPrimaryText.setVisibility(titleItem != null ? View.VISIBLE : View.GONE);
addSubtitle(titleItem != null /* hasTitle */);
mBottomDivider.setVisibility(mRowContent.hasBottomDivider() ? View.VISIBLE : View.GONE);
SliceItem primaryAction = mRowContent.getPrimaryAction();
if (primaryAction != null && primaryAction != mStartItem) {
mRowAction = new SliceActionImpl(primaryAction);
if (mRowAction.getSubtype() != null) {
switch (mRowAction.getSubtype()) {
case SUBTYPE_TOGGLE:
// If primary action is a toggle, add it and we're done
addAction(mRowAction, mTintColor, mEndContainer, false /* isStart */);
// TODO: if start item is tappable, touch feedback should exclude it
setViewClickable(mRootView, true);
return;
case SUBTYPE_DATE_PICKER:
setViewClickable(mRootView, true);
return;
case SUBTYPE_TIME_PICKER:
setViewClickable(mRootView, true);
return;
default:
}
}
}
final SliceItem range = mRowContent.getRange();
if (range != null) {
if (mRowAction != null) {
setViewClickable(mRootView, true);
}
mRangeItem = range;
SliceItem mode = SliceQuery.findSubtype(mRangeItem, FORMAT_INT, SUBTYPE_RANGE_MODE);
if (mode != null) {
mIsStarRating = mode.getInt() == STAR_RATING;
}
if (!skipSliderUpdate) {
initRangeBar();
addRangeView();
}
// if mStartItem is not null, then RowView should update end items.
if (mStartItem == null) {
return;
}
}
final SliceItem selection = mRowContent.getSelection();
if (selection != null) {
mSelectionItem = selection;
addSelection(selection);
return;
}
updateEndItems();
updateActionSpinner();
}
@SuppressWarnings("unchecked")
private void updateEndItems() {
if (mRowContent == null || (mRowContent.getRange() != null && mStartItem == null)) {
return;
}
mEndContainer.removeAllViews();
// If we're here we can can show end items; check for top level actions first
List endItems = mRowContent.getEndItems();
if (mHeaderActions != null) {
// Use these if we have them instead
endItems = mHeaderActions;
}
// Add start item to end of row for the top row if end items are empty and presenter
// doesn't show title items.
if (mRowContent.getIsHeader() && mStartItem != null && endItems.isEmpty()
&& !mRowContent.hasTitleItems()) {
endItems.add(mStartItem);
}
// If we're here we might be able to show end items
int endItemCount = 0;
boolean firstItemIsADefaultToggle = false;
boolean singleActionAtTheEnd = false;
SliceItem endAction = null;
for (int i = 0; i < endItems.size(); i++) {
final SliceItem endItem = (endItems.get(i) instanceof SliceItem)
? (SliceItem) endItems.get(i)
: ((SliceActionImpl) endItems.get(i)).getSliceItem();
if (endItemCount < MAX_END_ITEMS) {
if (addItem(endItem, mTintColor, false /* isStart */)) {
if (endAction == null && SliceQuery.find(endItem, FORMAT_ACTION) != null) {
endAction = endItem;
}
endItemCount++;
if (endItemCount == 1) {
firstItemIsADefaultToggle = !mToggles.isEmpty()
&& SliceQuery.find(endItem.getSlice(), FORMAT_IMAGE) == null;
singleActionAtTheEnd = endItems.size() == 1
&& SliceQuery.find(endItem, FORMAT_ACTION) != null;
}
}
}
}
mEndContainer.setVisibility(endItemCount > 0 ? VISIBLE : GONE);
// If there is a row action and the first end item is a default toggle, or action divider
// is set by presenter and a single action is at the end of the row, show the divider.
mActionDivider.setVisibility(mRowAction != null && (firstItemIsADefaultToggle
|| (mRowContent.hasActionDivider() && singleActionAtTheEnd))
? View.VISIBLE : View.GONE);
boolean hasStartAction = mStartItem != null
&& SliceQuery.find(mStartItem, FORMAT_ACTION) != null;
boolean hasEndItemAction = endAction != null;
boolean endAndRowActionTheSame = false;
if (mRowAction != null) {
setViewClickable(mRootView, true);
} else if (hasEndItemAction != hasStartAction && (endItemCount == 1 || hasStartAction)) {
// This row only has 1 action in start or end position; make whole row clickable for it
endAndRowActionTheSame = true;
if (!mToggles.isEmpty()) {
mRowAction = mToggles.keySet().iterator().next();
} else if (!mActions.isEmpty() && mActions.size() == 1) {
mRowAction = mActions.valueAt(0).getAction();
}
setViewClickable(mRootView, true);
}
if (mRowAction != null && !endAndRowActionTheSame
&& mLoadingActions.contains(mRowAction.getSliceItem())) {
mShowActionSpinner = true;
}
ViewCompat.setImportantForAccessibility(mRootView, mRootView.isClickable()
? ViewCompat.IMPORTANT_FOR_ACCESSIBILITY_AUTO
: ViewCompat.IMPORTANT_FOR_ACCESSIBILITY_NO
);
}
@Override
public void setLastUpdated(long lastUpdated) {
super.setLastUpdated(lastUpdated);
if (mRowContent != null) {
addSubtitle(mRowContent.getTitleItem() != null
&& TextUtils.isEmpty(mRowContent.getTitleItem().getSanitizedText()));
}
}
private void addSubtitle(boolean hasTitle) {
if (mRowContent == null || (mRowContent.getRange() != null && mStartItem != null)) {
return;
}
final SliceItem subtitleItem = getMode() == MODE_SMALL
? mRowContent.getSummaryItem()
: mRowContent.getSubtitleItem();
CharSequence subtitleTimeString = null;
if (mShowLastUpdated && mLastUpdated != -1) {
CharSequence relativeTime = getRelativeTimeString(mLastUpdated);
if (relativeTime != null) {
subtitleTimeString =
getResources().getString(R.string.abc_slice_updated, relativeTime);
}
}
CharSequence subtitle = subtitleItem != null ? subtitleItem.getSanitizedText() : null;
boolean subtitleExists = !TextUtils.isEmpty(subtitle)
|| (subtitleItem != null && subtitleItem.hasHint(HINT_PARTIAL));
if (subtitleExists) {
mSecondaryText.setText(subtitle);
if (mSliceStyle != null) {
mSecondaryText.setTextSize(TypedValue.COMPLEX_UNIT_PX, mIsHeader
? mSliceStyle.getHeaderSubtitleSize()
: mSliceStyle.getSubtitleSize());
mSecondaryText.setTextColor(mRowStyle.getSubtitleColor());
int verticalPadding = mIsHeader
? mSliceStyle.getVerticalHeaderTextPadding()
: mSliceStyle.getVerticalTextPadding();
mSecondaryText.setPadding(0, verticalPadding, 0, 0);
}
}
if (subtitleTimeString != null) {
if (!TextUtils.isEmpty(subtitle)) {
subtitleTimeString = " \u00B7 " + subtitleTimeString;
}
SpannableString sp = new SpannableString(subtitleTimeString);
sp.setSpan(new StyleSpan(Typeface.ITALIC), 0, subtitleTimeString.length(), 0);
mLastUpdatedText.setText(sp);
if (mSliceStyle != null) {
mLastUpdatedText.setTextSize(TypedValue.COMPLEX_UNIT_PX, mIsHeader
? mSliceStyle.getHeaderSubtitleSize() : mSliceStyle.getSubtitleSize());
mLastUpdatedText.setTextColor(mRowStyle.getSubtitleColor());
}
}
mLastUpdatedText.setVisibility(TextUtils.isEmpty(subtitleTimeString) ? GONE : VISIBLE);
mSecondaryText.setVisibility(subtitleExists ? VISIBLE : GONE);
// If this is non-header or something that can have 2 lines in the header (e.g. permission
// slice) then allow 2 lines if there's only a subtitle and now timestring.
boolean canHaveMultiLines = !mRowContent.getIsHeader() || mAllowTwoLines;
int maxLines = canHaveMultiLines && !hasTitle && subtitleExists
&& TextUtils.isEmpty(subtitleTimeString)
? 2 : 1;
mSecondaryText.setSingleLine(maxLines == 1);
mSecondaryText.setMaxLines(maxLines);
// TODO: Consider refactoring layout structure to avoid this
// Need to request a layout to update the weights for these views when RV recycles them
mSecondaryText.requestLayout();
mLastUpdatedText.requestLayout();
}
private CharSequence getRelativeTimeString(long time) {
long difference = System.currentTimeMillis() - time;
if (difference > DateUtils.YEAR_IN_MILLIS) {
int years = (int) (difference / DateUtils.YEAR_IN_MILLIS);
return getResources().getQuantityString(
R.plurals.abc_slice_duration_years, years, years);
} else if (difference > DateUtils.DAY_IN_MILLIS) {
int days = (int) (difference / DateUtils.DAY_IN_MILLIS);
return getResources().getQuantityString(R.plurals.abc_slice_duration_days, days, days);
} else if (difference > DateUtils.MINUTE_IN_MILLIS) {
int mins = (int) (difference / DateUtils.MINUTE_IN_MILLIS);
return getResources().getQuantityString(R.plurals.abc_slice_duration_min, mins, mins);
} else {
return null;
}
}
private void initRangeBar() {
SliceItem min = SliceQuery.findSubtype(mRangeItem, FORMAT_INT, SUBTYPE_MIN);
int minValue = 0;
if (min != null) {
minValue = min.getInt();
}
mRangeMinValue = minValue;
SliceItem max = SliceQuery.findSubtype(mRangeItem, FORMAT_INT, SUBTYPE_MAX);
int maxValue = mIsStarRating ? 5 : 100;
if (max != null) {
maxValue = max.getInt();
}
mRangeMaxValue = maxValue;
SliceItem progress = SliceQuery.findSubtype(mRangeItem, FORMAT_INT, SUBTYPE_VALUE);
int progressValue = 0;
if (progress != null) {
progressValue = progress.getInt() - minValue;
}
mRangeValue = progressValue;
}
@SuppressWarnings("deprecation")
private void addRangeView() {
if (mHandler == null) {
mHandler = new Handler();
}
if (mIsStarRating) {
addRatingBarView();
return;
}
// add either input slider or progress bar view
SliceItem style = SliceQuery.findSubtype(mRangeItem, FORMAT_INT, SUBTYPE_RANGE_MODE);
final boolean isIndeterminate = style != null && style.getInt() == INDETERMINATE_RANGE;
final boolean isSeekBar = FORMAT_ACTION.equals(mRangeItem.getFormat());
final boolean renderInNewLine = mStartItem == null;
ProgressBar progressBar;
if (isSeekBar) {
// If action and not star rating, must be input range.
if (renderInNewLine) {
progressBar = new SeekBar(getContext());
} else {
progressBar = (SeekBar) LayoutInflater.from(getContext()).inflate(
R.layout.abc_slice_seekbar_view, this, false);
if (mRowStyle != null) {
setViewWidth(progressBar, mRowStyle.getSeekBarInlineWidth());
}
}
} else {
// non interactive progress bar
if (renderInNewLine) {
progressBar = new ProgressBar(getContext(), null,
android.R.attr.progressBarStyleHorizontal);
} else {
progressBar = (ProgressBar) LayoutInflater.from(getContext()).inflate(
R.layout.abc_slice_progress_inline_view, this, false);
if (mRowStyle != null) {
setViewWidth(progressBar,
mRowStyle.getProgressBarInlineWidth());
setViewSidePaddings(progressBar,
mRowStyle.getProgressBarStartPadding(),
mRowStyle.getProgressBarEndPadding());
}
}
if (isIndeterminate) {
progressBar.setIndeterminate(true);
}
}
Drawable progressDrawable = isIndeterminate ? DrawableCompat.wrap(
progressBar.getIndeterminateDrawable()) :
DrawableCompat.wrap(progressBar.getProgressDrawable());
if (mTintColor != -1 && progressDrawable != null) {
DrawableCompat.setTint(progressDrawable, mTintColor);
if (isIndeterminate) {
progressBar.setIndeterminateDrawable(progressDrawable);
} else {
progressBar.setProgressDrawable(progressDrawable);
}
}
// N.B. We don't use progressBar.setMin because it doesn't work properly in backcompat
// and/or sliders.
progressBar.setMax(mRangeMaxValue - mRangeMinValue);
progressBar.setProgress(mRangeValue);
progressBar.setVisibility(View.VISIBLE);
if (mStartItem == null) {
addView(progressBar,
new LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.WRAP_CONTENT));
} else {
mSubContent.setVisibility(GONE);
mContent.addView(progressBar, 1);
}
mRangeBar = progressBar;
if (isSeekBar) {
SliceItem thumb = mRowContent.getInputRangeThumb();
SeekBar seekBar = (SeekBar) mRangeBar;
if (thumb != null && thumb.getIcon() != null) {
Drawable d = thumb.getIcon().loadDrawable(getContext());
if (d != null) {
seekBar.setThumb(d);
}
}
Drawable thumbDrawable = DrawableCompat.wrap(seekBar.getThumb());
if (mTintColor != -1 && thumbDrawable != null) {
DrawableCompat.setTint(thumbDrawable, mTintColor);
seekBar.setThumb(thumbDrawable);
}
seekBar.setOnSeekBarChangeListener(mSeekBarChangeListener);
}
}
private void addRatingBarView() {
RatingBar ratingBar = new RatingBar(getContext());
LayerDrawable starDrawable = (LayerDrawable) ratingBar.getProgressDrawable();
starDrawable.getDrawable(STAR_COLOR_INDEX).setColorFilter(mTintColor,
PorterDuff.Mode.SRC_IN);
ratingBar.setStepSize(1.0f);
ratingBar.setNumStars(mRangeMaxValue);
ratingBar.setRating(mRangeValue);
ratingBar.setVisibility(View.VISIBLE);
LinearLayout ratingBarContainer = new LinearLayout(getContext());
ratingBarContainer.setGravity(Gravity.CENTER);
ratingBarContainer.setVisibility(View.VISIBLE);
ratingBarContainer.addView(ratingBar,
new LayoutParams(LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT));
addView(ratingBarContainer, new LayoutParams(LayoutParams.MATCH_PARENT,
LayoutParams.WRAP_CONTENT));
ratingBar.setOnRatingBarChangeListener(mRatingBarChangeListener);
mRangeBar = ratingBarContainer;
}
void sendSliderValue() {
if (mRangeItem == null) {
return;
}
try {
mLastSentRangeUpdate = System.currentTimeMillis();
mRangeItem.fireAction(getContext(),
new Intent().addFlags(Intent.FLAG_RECEIVER_FOREGROUND)
.putExtra(EXTRA_RANGE_VALUE, mRangeValue));
if (mObserver != null) {
EventInfo info = new EventInfo(getMode(), ACTION_TYPE_SLIDER, ROW_TYPE_SLIDER,
mRowIndex);
info.state = mRangeValue;
mObserver.onSliceAction(info, mRangeItem);
}
} catch (CanceledException e) {
Log.e(TAG, "PendingIntent for slice cannot be sent", e);
}
}
@SuppressWarnings("deprecation")
private void addSelection(final SliceItem selection) {
if (mHandler == null) {
mHandler = new Handler();
}
mSelectionOptionKeys = new ArrayList<String>();
mSelectionOptionValues = new ArrayList<CharSequence>();
final List<SliceItem> optionItems = selection.getSlice().getItems();
for (int i = 0; i < optionItems.size(); i++) {
final SliceItem optionItem = optionItems.get(i);
if (!optionItem.hasHint(HINT_SELECTION_OPTION)) {
continue;
}
final SliceItem optionKeyItem =
SliceQuery.findSubtype(optionItem, FORMAT_TEXT, SUBTYPE_SELECTION_OPTION_KEY);
final SliceItem optionValueItem =
SliceQuery.findSubtype(optionItem, FORMAT_TEXT, SUBTYPE_SELECTION_OPTION_VALUE);
if (optionKeyItem == null || optionValueItem == null) {
continue;
}
mSelectionOptionKeys.add(optionKeyItem.getText().toString());
mSelectionOptionValues.add(optionValueItem.getSanitizedText());
}
mSelectionSpinner = (Spinner) LayoutInflater.from(getContext()).inflate(
R.layout.abc_slice_row_selection, this, false);
final ArrayAdapter<CharSequence> adapter = new ArrayAdapter<>(
getContext(), R.layout.abc_slice_row_selection_text, mSelectionOptionValues);
adapter.setDropDownViewResource(R.layout.abc_slice_row_selection_dropdown_text);
mSelectionSpinner.setAdapter(adapter);
addView(mSelectionSpinner);
// XXX: onItemSelected is called automatically.
mSelectionSpinner.setOnItemSelectedListener(this);
}
/**
* Add an action view to the container.
*/
private void addAction(final SliceActionImpl actionContent, int color, ViewGroup container,
boolean isStart) {
SliceActionView sav = new SliceActionView(getContext(), mSliceStyle, mRowStyle);
container.addView(sav);
if (container.getVisibility() == GONE) {
container.setVisibility(VISIBLE);
}
boolean isToggle = actionContent.isToggle();
int actionType = isToggle ? ACTION_TYPE_TOGGLE : ACTION_TYPE_BUTTON;
int rowType = isToggle ? ROW_TYPE_TOGGLE : ROW_TYPE_LIST;
EventInfo info = new EventInfo(getMode(), actionType, rowType, mRowIndex);
if (isStart) {
info.setPosition(EventInfo.POSITION_START, 0, 1);
}
sav.setAction(actionContent, info, mObserver, color, mLoadingListener);
if (mLoadingActions.contains(actionContent.getSliceItem())) {
sav.setLoading(true);
}
if (isToggle) {
mToggles.put(actionContent, sav);
} else {
mActions.put(actionContent, sav);
}
}
/**
* Adds simple items to a container. Simple items include actions with icons, images, or
* timestamps.
*/
private boolean addItem(SliceItem sliceItem, int color, boolean isStart) {
IconCompat icon = null;
SliceItem timeStamp = null;
ViewGroup container = isStart ? mStartContainer : mEndContainer;
if (FORMAT_SLICE.equals(sliceItem.getFormat())
|| FORMAT_ACTION.equals(sliceItem.getFormat())) {
if (sliceItem.hasHint(HINT_SHORTCUT)) {
addAction(new SliceActionImpl(sliceItem), color, container, isStart);
return true;
} else {
if (sliceItem.getSlice().getItems().size() == 0) {
return false;
}
sliceItem = sliceItem.getSlice().getItems().get(0);
}
}
if (FORMAT_IMAGE.equals(sliceItem.getFormat())) {
icon = sliceItem.getIcon();
} else if (FORMAT_LONG.equals(sliceItem.getFormat())) {
timeStamp = sliceItem;
}
View addedView = null;
if (icon != null) {
boolean isIcon = !sliceItem.hasHint(HINT_NO_TINT);
boolean useIntrinsicSize = sliceItem.hasHint(HINT_RAW);
final float density = getResources().getDisplayMetrics().density;
ImageView iv = new ImageView(getContext());
Drawable d = icon.loadDrawable(getContext());
final boolean hasRoundedImage =
mSliceStyle != null && mSliceStyle.getApplyCornerRadiusToLargeImages();
if (hasRoundedImage && sliceItem.hasHint(HINT_LARGE)) {
CornerDrawable cd = new CornerDrawable(d, mSliceStyle.getImageCornerRadius());
iv.setImageDrawable(cd);
} else {
iv.setImageDrawable(d);
}
if (isIcon && color != -1) {
iv.setColorFilter(color);
}
// Because of sliding, the title icon is added many times.
if (mIsRangeSliding) {
container.removeAllViews();
container.addView(iv);
} else {
container.addView(iv);
}
if (mRowStyle != null) {
int styleIconSize = mRowStyle.getIconSize();
mIconSize = styleIconSize > 0 ? styleIconSize : mIconSize;
int styleImageSize = mRowStyle.getImageSize();
mImageSize = styleImageSize > 0 ? styleImageSize : mImageSize;
}
LinearLayout.LayoutParams lp = (LinearLayout.LayoutParams) iv.getLayoutParams();
lp.width = useIntrinsicSize ? Math.round(d.getIntrinsicWidth() / density) : mImageSize;
lp.height = useIntrinsicSize ? Math.round(d.getIntrinsicHeight() / density) :
mImageSize;
iv.setLayoutParams(lp);
int p = 0;
if (isIcon) {
p = mImageSize == HEIGHT_UNBOUND
? mIconSize / 2 : (mImageSize - mIconSize) / 2;
}
iv.setPadding(p, p, p, p);
addedView = iv;
} else if (timeStamp != null) {
TextView tv = new TextView(getContext());
tv.setText(SliceViewUtil.getTimestampString(getContext(), sliceItem.getLong()));
if (mSliceStyle != null) {
tv.setTextSize(TypedValue.COMPLEX_UNIT_PX, mSliceStyle.getSubtitleSize());
tv.setTextColor(mRowStyle.getSubtitleColor());
}
container.addView(tv);
addedView = tv;
}
return addedView != null;
}
private void showSeeMore() {
final Button b = (Button) LayoutInflater.from(getContext()).inflate(
R.layout.abc_slice_row_show_more, this, false);
b.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
try {
if (mObserver != null) {
EventInfo info = new EventInfo(getMode(), EventInfo.ACTION_TYPE_SEE_MORE,
EventInfo.ROW_TYPE_LIST, mRowIndex);
mObserver.onSliceAction(info, mRowContent.getSliceItem());
}
mShowActionSpinner =
mRowContent.getSliceItem().fireActionInternal(getContext(), null);
if (mShowActionSpinner) {
if (mLoadingListener != null) {
mLoadingListener.onSliceActionLoading(mRowContent.getSliceItem(),
mRowIndex);
}
mLoadingActions.add(mRowContent.getSliceItem());
b.setVisibility(GONE);
}
updateActionSpinner();
} catch (CanceledException e) {
Log.e(TAG, "PendingIntent for slice cannot be sent", e);
}
}
});
if (mTintColor != -1) {
b.setTextColor(mTintColor);
}
mSeeMoreView = b;
mRootView.addView(mSeeMoreView);
if (mLoadingActions.contains(mRowContent.getSliceItem())) {
mShowActionSpinner = true;
b.setVisibility(GONE);
updateActionSpinner();
}
}
void updateActionSpinner() {
mActionSpinner.setVisibility(mShowActionSpinner ? VISIBLE : GONE);
}
@Override
public void setLoadingActions(Set<SliceItem> actions) {
if (actions == null) {
mLoadingActions.clear();
mShowActionSpinner = false;
} else {
mLoadingActions = actions;
}
updateEndItems();
updateActionSpinner();
}
@Override
public void onClick(View view) {
if (mRowAction == null || mRowAction.getActionItem() == null) {
return;
}
SliceActionView sav;
if (mRowAction.getSubtype() != null) {
switch (mRowAction.getSubtype()) {
case SUBTYPE_TOGGLE:
sav = mToggles.get(mRowAction);
break;
case SUBTYPE_DATE_PICKER:
onClickPicker(/*isDatePicker*/ true);
return;
case SUBTYPE_TIME_PICKER:
onClickPicker(/*isDatePicker*/ false);
return;
default:
sav = mActions.get(mRowAction);
}
} else {
sav = mActions.get(mRowAction);
}
if (sav != null && !(view instanceof SliceActionView)) {
// Row might have a single action item set on it, in that case we activate that item
// and it will handle displaying any loading states / updating state for toggles
sav.sendAction();
} else {
if (mRowContent.getIsHeader()) {
// Header clicks are a little weird and SliceView needs to know about them to
// maintain loading state; this is hooked up in SliceAdapter -- it will call
// through to SliceView parent which has the info to perform the click.
performClick();
} else {
try {
mShowActionSpinner =
mRowAction.getActionItem().fireActionInternal(getContext(), null);
if (mObserver != null) {
EventInfo info = new EventInfo(getMode(), EventInfo.ACTION_TYPE_CONTENT,
EventInfo.ROW_TYPE_LIST, mRowIndex);
mObserver.onSliceAction(info, mRowAction.getSliceItem());
}
if (mShowActionSpinner && mLoadingListener != null) {
mLoadingListener.onSliceActionLoading(mRowAction.getSliceItem(), mRowIndex);
mLoadingActions.add(mRowAction.getSliceItem());
}
updateActionSpinner();
} catch (CanceledException e) {
Log.e(TAG, "PendingIntent for slice cannot be sent", e);
}
}
}
}
private void onClickPicker(boolean isDatePicker) {
if (mRowAction == null) {
return;
}
Log.d("ASDF", "ASDF" + isDatePicker + ":" + mRowAction.getSliceItem());
SliceItem dateTimeItem = SliceQuery.findSubtype(mRowAction.getSliceItem(), FORMAT_LONG,
SUBTYPE_MILLIS);
if (dateTimeItem == null) {
return;
}
int rowIndex = mRowIndex;
Calendar cal = Calendar.getInstance();
cal.setTime(new Date(dateTimeItem.getLong()));
if (isDatePicker) {
DatePickerDialog dialog = new DatePickerDialog(
getContext(),
R.style.DialogTheme,
new DateSetListener(mRowAction.getSliceItem(), 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(mRowAction.getSliceItem(), rowIndex),
cal.get(Calendar.HOUR_OF_DAY),
cal.get(Calendar.MINUTE),
false);
dialog.show();
}
}
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);
}
}
}
}
@Override
public void onItemSelected(AdapterView<?> parent, View view, int position, long id) {
if (mSelectionItem == null
|| parent != mSelectionSpinner
|| position < 0
|| position >= mSelectionOptionKeys.size()) {
return;
}
if (mObserver != null) {
EventInfo info = new EventInfo(getMode(), ACTION_TYPE_SELECTION, ROW_TYPE_SELECTION,
mRowIndex);
// TODO: Record selected item somehow?
mObserver.onSliceAction(info, mSelectionItem);
}
final String optionKey = mSelectionOptionKeys.get(position);
try {
final boolean loading = mSelectionItem.fireActionInternal(getContext(),
new Intent().addFlags(Intent.FLAG_RECEIVER_FOREGROUND)
.putExtra(EXTRA_SELECTION, optionKey));
if (loading) {
mShowActionSpinner = true;
if (mLoadingListener != null) {
mLoadingListener.onSliceActionLoading(mRowAction.getSliceItem(), mRowIndex);
mLoadingActions.add(mRowAction.getSliceItem());
}
updateActionSpinner();
}
} catch (CanceledException e) {
Log.e(TAG, "PendingIntent for slice cannot be sent", e);
}
}
@Override
public void onNothingSelected(@NonNull AdapterView<?> parent) {
}
private void setViewClickable(View layout, boolean isClickable) {
layout.setOnClickListener(isClickable ? this : null);
layout.setBackground(isClickable
? SliceViewUtil.getDrawable(getContext(), android.R.attr.selectableItemBackground)
: null);
layout.setClickable(isClickable);
}
@Override
public void resetView() {
mRowContent = null;
mLoadingActions.clear();
resetViewState();
}
private void resetViewState() {
mRootView.setVisibility(VISIBLE);
setLayoutDirection(View.LAYOUT_DIRECTION_INHERIT);
setViewClickable(mRootView, false);
setViewClickable(mContent, false);
mStartContainer.removeAllViews();
mEndContainer.removeAllViews();
mEndContainer.setVisibility(GONE);
mPrimaryText.setText(null);
mSecondaryText.setText(null);
mLastUpdatedText.setText(null);
mLastUpdatedText.setVisibility(GONE);
mToggles.clear();
mActions.clear();
mRowAction = null;
mBottomDivider.setVisibility(GONE);
mActionDivider.setVisibility(GONE);
if (mSeeMoreView != null) {
mRootView.removeView(mSeeMoreView);
mSeeMoreView = null;
}
mIsRangeSliding = false;
mRangeHasPendingUpdate = false;
mRangeItem = null;
mRangeMinValue = 0;
mRangeMaxValue = 0;
mRangeValue = 0;
mLastSentRangeUpdate = 0;
mHandler = null;
if (mRangeBar != null) {
if (mStartItem == null) {
removeView(mRangeBar);
} else {
mContent.removeView(mRangeBar);
}
mRangeBar = null;
}
mSubContent.setVisibility(VISIBLE);
mStartItem = null;
mActionSpinner.setVisibility(GONE);
if (mSelectionSpinner != null) {
removeView(mSelectionSpinner);
mSelectionSpinner = null;
}
mSelectionItem = null;
}
Runnable mRangeUpdater = new Runnable() {
@Override
public void run() {
sendSliderValue();
mRangeUpdaterRunning = false;
}
};
private final SeekBar.OnSeekBarChangeListener mSeekBarChangeListener =
new SeekBar.OnSeekBarChangeListener() {
@Override
public void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser) {
mRangeValue = progress + mRangeMinValue;
final long now = System.currentTimeMillis();
if (mLastSentRangeUpdate != 0 && now - mLastSentRangeUpdate > SLIDER_INTERVAL) {
mRangeUpdaterRunning = false;
mHandler.removeCallbacks(mRangeUpdater);
sendSliderValue();
} else if (!mRangeUpdaterRunning) {
mRangeUpdaterRunning = true;
mHandler.postDelayed(mRangeUpdater, SLIDER_INTERVAL);
}
}
@Override
public void onStartTrackingTouch(SeekBar seekBar) {
mIsRangeSliding = true;
}
@Override
public void onStopTrackingTouch(SeekBar seekBar) {
mIsRangeSliding = false;
if (mRangeUpdaterRunning || mRangeHasPendingUpdate) {
mRangeUpdaterRunning = false;
mRangeHasPendingUpdate = false;
mHandler.removeCallbacks(mRangeUpdater);
mRangeValue = seekBar.getProgress() + mRangeMinValue;
sendSliderValue();
}
}
};
private final RatingBar.OnRatingBarChangeListener mRatingBarChangeListener =
new RatingBar.OnRatingBarChangeListener() {
@Override
public void onRatingChanged(RatingBar ratingBar, float rating, boolean fromUser) {
mRangeValue = Math.round(rating + mRangeMinValue);
final long now = System.currentTimeMillis();
if (mLastSentRangeUpdate != 0 && now - mLastSentRangeUpdate > SLIDER_INTERVAL) {
mRangeUpdaterRunning = false;
mHandler.removeCallbacks(mRangeUpdater);
sendSliderValue();
} else if (!mRangeUpdaterRunning) {
mRangeUpdaterRunning = true;
mHandler.postDelayed(mRangeUpdater, SLIDER_INTERVAL);
}
}
};
}