| /* |
| * 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.HINT_HORIZONTAL; |
| import static android.app.slice.Slice.SUBTYPE_MESSAGE; |
| import static android.app.slice.Slice.SUBTYPE_SOURCE; |
| import static android.app.slice.SliceItem.FORMAT_INT; |
| import static android.app.slice.SliceItem.FORMAT_TEXT; |
| |
| import static androidx.slice.widget.SliceView.MODE_LARGE; |
| |
| import android.app.slice.Slice; |
| import android.content.Context; |
| import android.util.AttributeSet; |
| import android.view.LayoutInflater; |
| import android.view.MotionEvent; |
| import android.view.View; |
| import android.view.ViewGroup; |
| import android.view.ViewGroup.LayoutParams; |
| |
| import androidx.annotation.RestrictTo; |
| import androidx.collection.ArrayMap; |
| import androidx.recyclerview.widget.RecyclerView; |
| import androidx.slice.SliceItem; |
| import androidx.slice.core.SliceQuery; |
| import androidx.slice.view.R; |
| |
| import java.util.ArrayList; |
| import java.util.Iterator; |
| import java.util.List; |
| |
| /** |
| * @hide |
| */ |
| @RestrictTo(RestrictTo.Scope.LIBRARY) |
| public class LargeSliceAdapter extends RecyclerView.Adapter<LargeSliceAdapter.SliceViewHolder> { |
| |
| static final int TYPE_DEFAULT = 1; |
| static final int TYPE_HEADER = 2; // TODO: headers shouldn't scroll off |
| static final int TYPE_GRID = 3; |
| static final int TYPE_MESSAGE = 4; |
| static final int TYPE_MESSAGE_LOCAL = 5; |
| |
| static final int HEADER_INDEX = 0; |
| |
| private final IdGenerator mIdGen = new IdGenerator(); |
| private final Context mContext; |
| private List<SliceWrapper> mSlices = new ArrayList<>(); |
| private SliceView.OnSliceActionListener mSliceObserver; |
| private int mColor; |
| private AttributeSet mAttrs; |
| private int mDefStyleAttr; |
| private int mDefStyleRes; |
| private List<SliceItem> mSliceActions; |
| private boolean mShowLastUpdated; |
| private long mLastUpdated; |
| private SliceView mParent; |
| private LargeTemplateView mTemplateView; |
| |
| public LargeSliceAdapter(Context context) { |
| mContext = context; |
| setHasStableIds(true); |
| } |
| |
| /** |
| * Sets the SliceView parent and the template parent. |
| */ |
| public void setParents(SliceView parent, LargeTemplateView templateView) { |
| mParent = parent; |
| mTemplateView = templateView; |
| } |
| |
| /** |
| * Sets the observer to pass down to child views. |
| */ |
| public void setSliceObserver(SliceView.OnSliceActionListener observer) { |
| mSliceObserver = observer; |
| } |
| |
| /** |
| * Sets the actions to display for this slice, this adjusts what's displayed in the header item. |
| */ |
| public void setSliceActions(List<SliceItem> actions) { |
| mSliceActions = actions; |
| notifyHeaderChanged(); |
| } |
| |
| /** |
| * Set the {@link SliceItem}'s to be displayed in the adapter and the accent color. |
| */ |
| public void setSliceItems(List<SliceItem> slices, int color) { |
| if (slices == null) { |
| mSlices.clear(); |
| } else { |
| mIdGen.resetUsage(); |
| mSlices = new ArrayList<>(slices.size()); |
| for (SliceItem s : slices) { |
| mSlices.add(new SliceWrapper(s, mIdGen)); |
| } |
| } |
| mColor = color; |
| notifyDataSetChanged(); |
| } |
| |
| /** |
| * Sets the attribute set to use for views in the list. |
| */ |
| public void setStyle(AttributeSet attrs, int defStyleAttr, int defStyleRes) { |
| mAttrs = attrs; |
| mDefStyleAttr = defStyleAttr; |
| mDefStyleRes = defStyleRes; |
| notifyDataSetChanged(); |
| } |
| |
| /** |
| * Sets whether the last updated time should be shown on the slice. |
| */ |
| public void setShowLastUpdated(boolean showLastUpdated) { |
| mShowLastUpdated = showLastUpdated; |
| notifyHeaderChanged(); |
| } |
| |
| /** |
| * Sets when the slice was last updated. |
| */ |
| public void setLastUpdated(long lastUpdated) { |
| mLastUpdated = lastUpdated; |
| notifyHeaderChanged(); |
| } |
| |
| @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); |
| holder.bind(slice.mItem, position); |
| } |
| |
| private void notifyHeaderChanged() { |
| if (getItemCount() > 0) { |
| notifyItemChanged(HEADER_INDEX); |
| } |
| } |
| |
| private View inflateForType(int viewType) { |
| View v = new RowView(mContext); |
| switch (viewType) { |
| case TYPE_GRID: |
| v = LayoutInflater.from(mContext).inflate(R.layout.abc_slice_grid, null); |
| break; |
| case TYPE_MESSAGE: |
| v = LayoutInflater.from(mContext).inflate(R.layout.abc_slice_message, null); |
| break; |
| case TYPE_MESSAGE_LOCAL: |
| v = LayoutInflater.from(mContext).inflate(R.layout.abc_slice_message_local, |
| null); |
| break; |
| } |
| int mode = mParent != null ? mParent.getMode() : MODE_LARGE; |
| ((SliceChildView) v).setMode(mode); |
| return v; |
| } |
| |
| 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 = getFormat(item); |
| mId = idGen.getId(item); |
| } |
| |
| public static int getFormat(SliceItem item) { |
| if (SUBTYPE_MESSAGE.equals(item.getSubType())) { |
| // TODO: Better way to determine me or not? Something more like Messaging style. |
| if (SliceQuery.findSubtype(item, null, SUBTYPE_SOURCE) != null) { |
| return TYPE_MESSAGE; |
| } else { |
| return TYPE_MESSAGE_LOCAL; |
| } |
| } |
| if (item.hasHint(HINT_HORIZONTAL)) { |
| return TYPE_GRID; |
| } |
| if (!item.hasHint(Slice.HINT_LIST_ITEM)) { |
| return TYPE_HEADER; |
| } |
| return TYPE_DEFAULT; |
| } |
| } |
| |
| /** |
| * A {@link RecyclerView.ViewHolder} for presenting slices in {@link LargeSliceAdapter}. |
| */ |
| public class SliceViewHolder extends RecyclerView.ViewHolder implements View.OnTouchListener, |
| View.OnClickListener { |
| public final SliceChildView mSliceChildView; |
| |
| public SliceViewHolder(View itemView) { |
| super(itemView); |
| mSliceChildView = itemView instanceof SliceChildView ? (SliceChildView) itemView : null; |
| } |
| |
| void bind(SliceItem item, int position) { |
| if (mSliceChildView == null || item == null) { |
| return; |
| } |
| // Click listener used to pipe click events to parent |
| mSliceChildView.setOnClickListener(this); |
| // Touch listener used to pipe events to touch feedback drawable |
| mSliceChildView.setOnTouchListener(this); |
| |
| final boolean isHeader = position == HEADER_INDEX; |
| mSliceChildView.setTint(mColor); |
| mSliceChildView.setStyle(mAttrs, mDefStyleAttr, mDefStyleRes); |
| mSliceChildView.setSliceItem(item, isHeader, position, mSliceObserver); |
| if (isHeader && mSliceChildView instanceof RowView) { |
| mSliceChildView.setSliceActions(mSliceActions); |
| mSliceChildView.setLastUpdated(mLastUpdated); |
| mSliceChildView.setShowLastUpdated(mShowLastUpdated); |
| } |
| int[] info = new int[2]; |
| info[0] = ListContent.getRowType(mContext, item, isHeader, mSliceActions); |
| info[1] = position; |
| mSliceChildView.setTag(info); |
| } |
| |
| @Override |
| public void onClick(View v) { |
| if (mParent != null) { |
| mParent.setClickInfo((int[]) v.getTag()); |
| mParent.performClick(); |
| } |
| } |
| |
| @Override |
| public boolean onTouch(View v, MotionEvent event) { |
| if (mTemplateView != null) { |
| mTemplateView.onForegroundActivated(event); |
| } |
| return false; |
| } |
| } |
| |
| 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); |
| Integer usedIdIndex = mUsedIds.get(str); |
| int index = usedIdIndex != null ? usedIdIndex : 0; |
| mUsedIds.put(str, index + 1); |
| return id + index * 10000; |
| } |
| |
| private String genString(SliceItem item) { |
| final StringBuilder builder = new StringBuilder(); |
| Iterator<SliceItem> items = SliceQuery.stream(item); |
| while (items.hasNext()) { |
| SliceItem i = items.next(); |
| builder.append(i.getFormat()); |
| builder.append(i.getHints()); |
| switch (i.getFormat()) { |
| case FORMAT_TEXT: |
| builder.append(i.getText()); |
| break; |
| case FORMAT_INT: |
| builder.append(i.getInt()); |
| break; |
| } |
| } |
| return builder.toString(); |
| } |
| |
| public void resetUsage() { |
| mUsedIds.clear(); |
| } |
| } |
| } |