Home | History | Annotate | Download | only in widget
      1 /*
      2  * Copyright 2017 The Android Open Source Project
      3  *
      4  * Licensed under the Apache License, Version 2.0 (the "License");
      5  * you may not use this file except in compliance with the License.
      6  * You may obtain a copy of the License at
      7  *
      8  *      http://www.apache.org/licenses/LICENSE-2.0
      9  *
     10  * Unless required by applicable law or agreed to in writing, software
     11  * distributed under the License is distributed on an "AS IS" BASIS,
     12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
     13  * See the License for the specific language governing permissions and
     14  * limitations under the License.
     15  */
     16 
     17 package androidx.slice.widget;
     18 
     19 import static android.app.slice.Slice.HINT_HORIZONTAL;
     20 import static android.app.slice.Slice.HINT_SUMMARY;
     21 import static android.app.slice.Slice.SUBTYPE_MESSAGE;
     22 import static android.app.slice.Slice.SUBTYPE_SOURCE;
     23 import static android.app.slice.SliceItem.FORMAT_INT;
     24 import static android.app.slice.SliceItem.FORMAT_TEXT;
     25 
     26 import static androidx.slice.widget.SliceView.MODE_LARGE;
     27 
     28 import android.app.slice.Slice;
     29 import android.content.Context;
     30 import android.util.AttributeSet;
     31 import android.view.LayoutInflater;
     32 import android.view.MotionEvent;
     33 import android.view.View;
     34 import android.view.ViewGroup;
     35 import android.view.ViewGroup.LayoutParams;
     36 
     37 import androidx.annotation.RestrictTo;
     38 import androidx.collection.ArrayMap;
     39 import androidx.recyclerview.widget.RecyclerView;
     40 import androidx.slice.SliceItem;
     41 import androidx.slice.core.SliceQuery;
     42 import androidx.slice.view.R;
     43 
     44 import java.util.ArrayList;
     45 import java.util.Iterator;
     46 import java.util.List;
     47 
     48 /**
     49  * @hide
     50  */
     51 @RestrictTo(RestrictTo.Scope.LIBRARY)
     52 public class LargeSliceAdapter extends RecyclerView.Adapter<LargeSliceAdapter.SliceViewHolder> {
     53 
     54     static final int TYPE_DEFAULT       = 1;
     55     static final int TYPE_HEADER        = 2; // TODO: headers shouldn't scroll off
     56     static final int TYPE_GRID          = 3;
     57     static final int TYPE_MESSAGE       = 4;
     58     static final int TYPE_MESSAGE_LOCAL = 5;
     59 
     60     static final int HEADER_INDEX = 0;
     61 
     62     private final IdGenerator mIdGen = new IdGenerator();
     63     private final Context mContext;
     64     private List<SliceWrapper> mSlices = new ArrayList<>();
     65     private SliceView.OnSliceActionListener mSliceObserver;
     66     private int mColor;
     67     private AttributeSet mAttrs;
     68     private int mDefStyleAttr;
     69     private int mDefStyleRes;
     70     private List<SliceItem> mSliceActions;
     71     private boolean mShowLastUpdated;
     72     private long mLastUpdated;
     73     private SliceView mParent;
     74     private LargeTemplateView mTemplateView;
     75 
     76     public LargeSliceAdapter(Context context) {
     77         mContext = context;
     78         setHasStableIds(true);
     79     }
     80 
     81     /**
     82      * Sets the SliceView parent and the template parent.
     83      */
     84     public void setParents(SliceView parent, LargeTemplateView templateView) {
     85         mParent = parent;
     86         mTemplateView = templateView;
     87     }
     88 
     89     /**
     90      * Sets the observer to pass down to child views.
     91      */
     92     public void setSliceObserver(SliceView.OnSliceActionListener observer) {
     93         mSliceObserver = observer;
     94     }
     95 
     96     /**
     97      * Sets the actions to display for this slice, this adjusts what's displayed in the header item.
     98      */
     99     public void setSliceActions(List<SliceItem> actions) {
    100         mSliceActions = actions;
    101         notifyHeaderChanged();
    102     }
    103 
    104     /**
    105      * Set the {@link SliceItem}'s to be displayed in the adapter and the accent color.
    106      */
    107     public void setSliceItems(List<SliceItem> slices, int color, int mode) {
    108         if (slices == null) {
    109             mSlices.clear();
    110         } else {
    111             mIdGen.resetUsage();
    112             mSlices = new ArrayList<>(slices.size());
    113             for (SliceItem s : slices) {
    114                 mSlices.add(new SliceWrapper(s, mIdGen, mode));
    115             }
    116         }
    117         mColor = color;
    118         notifyDataSetChanged();
    119     }
    120 
    121     /**
    122      * Sets the attribute set to use for views in the list.
    123      */
    124     public void setStyle(AttributeSet attrs, int defStyleAttr, int defStyleRes) {
    125         mAttrs = attrs;
    126         mDefStyleAttr = defStyleAttr;
    127         mDefStyleRes = defStyleRes;
    128         notifyDataSetChanged();
    129     }
    130 
    131     /**
    132      * Sets whether the last updated time should be shown on the slice.
    133      */
    134     public void setShowLastUpdated(boolean showLastUpdated) {
    135         mShowLastUpdated = showLastUpdated;
    136         notifyHeaderChanged();
    137     }
    138 
    139     /**
    140      * Sets when the slice was last updated.
    141      */
    142     public void setLastUpdated(long lastUpdated) {
    143         mLastUpdated = lastUpdated;
    144         notifyHeaderChanged();
    145     }
    146 
    147     @Override
    148     public SliceViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
    149         View v = inflateForType(viewType);
    150         v.setLayoutParams(new LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.WRAP_CONTENT));
    151         return new SliceViewHolder(v);
    152     }
    153 
    154     @Override
    155     public int getItemViewType(int position) {
    156         return mSlices.get(position).mType;
    157     }
    158 
    159     @Override
    160     public long getItemId(int position) {
    161         return mSlices.get(position).mId;
    162     }
    163 
    164     @Override
    165     public int getItemCount() {
    166         return mSlices.size();
    167     }
    168 
    169     @Override
    170     public void onBindViewHolder(SliceViewHolder holder, int position) {
    171         SliceWrapper slice = mSlices.get(position);
    172         holder.bind(slice.mItem, position);
    173     }
    174 
    175     private void notifyHeaderChanged() {
    176         if (getItemCount() > 0) {
    177             notifyItemChanged(HEADER_INDEX);
    178         }
    179     }
    180 
    181     private View inflateForType(int viewType) {
    182         View v = new RowView(mContext);
    183         switch (viewType) {
    184             case TYPE_GRID:
    185                 v = LayoutInflater.from(mContext).inflate(R.layout.abc_slice_grid, null);
    186                 break;
    187             case TYPE_MESSAGE:
    188                 v = LayoutInflater.from(mContext).inflate(R.layout.abc_slice_message, null);
    189                 break;
    190             case TYPE_MESSAGE_LOCAL:
    191                 v = LayoutInflater.from(mContext).inflate(R.layout.abc_slice_message_local,
    192                         null);
    193                 break;
    194         }
    195         return v;
    196     }
    197 
    198     protected static class SliceWrapper {
    199         private final SliceItem mItem;
    200         private final int mType;
    201         private final long mId;
    202 
    203         public SliceWrapper(SliceItem item, IdGenerator idGen, int mode) {
    204             mItem = item;
    205             mType = getFormat(item);
    206             mId = idGen.getId(item, mode);
    207         }
    208 
    209         public static int getFormat(SliceItem item) {
    210             if (SUBTYPE_MESSAGE.equals(item.getSubType())) {
    211                 // TODO: Better way to determine me or not? Something more like Messaging style.
    212                 if (SliceQuery.findSubtype(item, null, SUBTYPE_SOURCE) != null) {
    213                     return TYPE_MESSAGE;
    214                 } else {
    215                     return TYPE_MESSAGE_LOCAL;
    216                 }
    217             }
    218             if (item.hasHint(HINT_HORIZONTAL)) {
    219                 return TYPE_GRID;
    220             }
    221             if (!item.hasHint(Slice.HINT_LIST_ITEM)) {
    222                 return TYPE_HEADER;
    223             }
    224             return TYPE_DEFAULT;
    225         }
    226     }
    227 
    228     /**
    229      * A {@link RecyclerView.ViewHolder} for presenting slices in {@link LargeSliceAdapter}.
    230      */
    231     public class SliceViewHolder extends RecyclerView.ViewHolder implements View.OnTouchListener,
    232             View.OnClickListener {
    233         public final SliceChildView mSliceChildView;
    234 
    235         public SliceViewHolder(View itemView) {
    236             super(itemView);
    237             mSliceChildView = itemView instanceof SliceChildView ? (SliceChildView) itemView : null;
    238         }
    239 
    240         void bind(SliceItem item, int position) {
    241             if (mSliceChildView == null || item == null) {
    242                 return;
    243             }
    244             // Click listener used to pipe click events to parent
    245             mSliceChildView.setOnClickListener(this);
    246             // Touch listener used to pipe events to touch feedback drawable
    247             mSliceChildView.setOnTouchListener(this);
    248 
    249             final boolean isHeader = position == HEADER_INDEX;
    250             int mode = mParent != null ? mParent.getMode() : MODE_LARGE;
    251             mSliceChildView.setMode(mode);
    252             mSliceChildView.setTint(mColor);
    253             mSliceChildView.setStyle(mAttrs, mDefStyleAttr, mDefStyleRes);
    254             mSliceChildView.setSliceItem(item, isHeader, position, getItemCount(), mSliceObserver);
    255             mSliceChildView.setSliceActions(isHeader ? mSliceActions : null);
    256             mSliceChildView.setLastUpdated(isHeader ? mLastUpdated : -1);
    257             mSliceChildView.setShowLastUpdated(isHeader && mShowLastUpdated);
    258             if (mSliceChildView instanceof RowView) {
    259                 ((RowView) mSliceChildView).setSingleItem(getItemCount() == 1);
    260             }
    261             int[] info = new int[2];
    262             info[0] = ListContent.getRowType(mContext, item, isHeader, mSliceActions);
    263             info[1] = position;
    264             mSliceChildView.setTag(info);
    265         }
    266 
    267         @Override
    268         public void onClick(View v) {
    269             if (mParent != null) {
    270                 mParent.setClickInfo((int[]) v.getTag());
    271                 mParent.performClick();
    272             }
    273         }
    274 
    275         @Override
    276         public boolean onTouch(View v, MotionEvent event) {
    277             if (mTemplateView != null) {
    278                 mTemplateView.onForegroundActivated(event);
    279             }
    280             return false;
    281         }
    282     }
    283 
    284     private static class IdGenerator {
    285         private long mNextLong = 0;
    286         private final ArrayMap<String, Long> mCurrentIds = new ArrayMap<>();
    287         private final ArrayMap<String, Integer> mUsedIds = new ArrayMap<>();
    288 
    289         public long getId(SliceItem item, int mode) {
    290             String str = genString(item);
    291             SliceItem summary = SliceQuery.find(item, null, HINT_SUMMARY, null);
    292             if (summary != null) {
    293                 str += mode; // mode matters
    294             }
    295             if (!mCurrentIds.containsKey(str)) {
    296                 mCurrentIds.put(str, mNextLong++);
    297             }
    298             long id = mCurrentIds.get(str);
    299             Integer usedIdIndex = mUsedIds.get(str);
    300             int index = usedIdIndex != null ? usedIdIndex : 0;
    301             mUsedIds.put(str, index + 1);
    302             return id + index * 10000;
    303         }
    304 
    305         private String genString(SliceItem item) {
    306             final StringBuilder builder = new StringBuilder();
    307             Iterator<SliceItem> items = SliceQuery.stream(item);
    308             while (items.hasNext()) {
    309                 SliceItem i = items.next();
    310                 builder.append(i.getFormat());
    311                 builder.append(i.getHints());
    312                 switch (i.getFormat()) {
    313                     case FORMAT_TEXT:
    314                         builder.append(i.getText());
    315                         break;
    316                     case FORMAT_INT:
    317                         builder.append(i.getInt());
    318                         break;
    319                 }
    320             }
    321             return builder.toString();
    322         }
    323 
    324         public void resetUsage() {
    325             mUsedIds.clear();
    326         }
    327     }
    328 }
    329