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_NO_TINT;
     20 import static android.app.slice.SliceItem.FORMAT_IMAGE;
     21 import static android.app.slice.SliceItem.FORMAT_REMOTE_INPUT;
     22 
     23 import static androidx.slice.core.SliceHints.ICON_IMAGE;
     24 
     25 import android.app.PendingIntent.CanceledException;
     26 import android.app.RemoteInput;
     27 import android.app.slice.Slice;
     28 import android.content.Context;
     29 import android.content.res.ColorStateList;
     30 import android.graphics.Color;
     31 import android.os.Build;
     32 import android.util.Log;
     33 import android.util.TypedValue;
     34 import android.view.View;
     35 import android.view.ViewParent;
     36 import android.widget.FrameLayout;
     37 import android.widget.ImageView;
     38 import android.widget.ImageView.ScaleType;
     39 import android.widget.LinearLayout;
     40 import android.widget.TextView;
     41 
     42 import androidx.annotation.NonNull;
     43 import androidx.annotation.RequiresApi;
     44 import androidx.annotation.RestrictTo;
     45 import androidx.core.graphics.drawable.IconCompat;
     46 import androidx.core.widget.ImageViewCompat;
     47 import androidx.slice.SliceItem;
     48 import androidx.slice.core.SliceActionImpl;
     49 import androidx.slice.core.SliceQuery;
     50 
     51 import java.util.List;
     52 
     53 /**
     54  * @hide
     55  */
     56 @RestrictTo(RestrictTo.Scope.LIBRARY)
     57 public class ActionRow extends FrameLayout {
     58 
     59     private static final int MAX_ACTIONS = 5;
     60     private static final String TAG = "ActionRow";
     61 
     62     private final int mSize;
     63     private final int mIconPadding;
     64     private final LinearLayout mActionsGroup;
     65     private final boolean mFullActions;
     66     private int mColor = Color.BLACK;
     67 
     68     public ActionRow(Context context, boolean fullActions) {
     69         super(context);
     70         mFullActions = fullActions;
     71         mSize = (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 48,
     72                 context.getResources().getDisplayMetrics());
     73         mIconPadding = (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 12,
     74                 context.getResources().getDisplayMetrics());
     75         mActionsGroup = new LinearLayout(context);
     76         mActionsGroup.setOrientation(LinearLayout.HORIZONTAL);
     77         mActionsGroup.setLayoutParams(
     78                 new LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.WRAP_CONTENT));
     79         addView(mActionsGroup);
     80     }
     81 
     82     private void setColor(int color) {
     83         mColor = color;
     84         for (int i = 0; i < mActionsGroup.getChildCount(); i++) {
     85             View view = mActionsGroup.getChildAt(i);
     86             int mode = (Integer) view.getTag();
     87             boolean tint = mode == ICON_IMAGE;
     88             if (tint) {
     89                 ImageViewCompat.setImageTintList((ImageView) view, ColorStateList.valueOf(mColor));
     90             }
     91         }
     92     }
     93 
     94     private ImageView addAction(IconCompat icon, boolean allowTint) {
     95         ImageView imageView = new ImageView(getContext());
     96         imageView.setPadding(mIconPadding, mIconPadding, mIconPadding, mIconPadding);
     97         imageView.setScaleType(ScaleType.FIT_CENTER);
     98         imageView.setImageDrawable(icon.loadDrawable(getContext()));
     99         if (allowTint) {
    100             ImageViewCompat.setImageTintList(imageView, ColorStateList.valueOf(mColor));
    101         }
    102         imageView.setBackground(SliceViewUtil.getDrawable(getContext(),
    103                 android.R.attr.selectableItemBackground));
    104         imageView.setTag(allowTint);
    105         addAction(imageView);
    106         return imageView;
    107     }
    108 
    109     /**
    110      * Set the actions and color for this action row.
    111      */
    112     public void setActions(@NonNull List<SliceItem> actions, int color) {
    113         removeAllViews();
    114         mActionsGroup.removeAllViews();
    115         addView(mActionsGroup);
    116         if (color != -1) {
    117             setColor(color);
    118         }
    119         for (final SliceItem action : actions) {
    120             if (mActionsGroup.getChildCount() >= MAX_ACTIONS) {
    121                 return;
    122             }
    123             final SliceItem input = SliceQuery.find(action, FORMAT_REMOTE_INPUT);
    124             final SliceItem image = SliceQuery.find(action, FORMAT_IMAGE);
    125             if (input != null && image != null) {
    126                 if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
    127                     handleSetRemoteInputActions(input, image, action);
    128                 } else {
    129                     Log.w(TAG, "Received RemoteInput on API <20 " + input);
    130                 }
    131             } else if (action.hasHint(Slice.HINT_SHORTCUT)) {
    132                 final SliceActionImpl ac = new SliceActionImpl(action);
    133                 IconCompat iconItem = ac.getIcon();
    134                 if (iconItem != null && ac.getActionItem() != null) {
    135                     boolean tint = ac.getImageMode() == ICON_IMAGE;
    136                     addAction(iconItem, tint).setOnClickListener(
    137                             new OnClickListener() {
    138                                 @Override
    139                                 public void onClick(View v) {
    140                                     try {
    141                                         // TODO - should log events here
    142                                         ac.getActionItem().fireAction(null, null);
    143                                     } catch (CanceledException e) {
    144                                         e.printStackTrace();
    145                                     }
    146                                 }
    147                             });
    148                 }
    149             }
    150         }
    151         setVisibility(getChildCount() != 0 ? View.VISIBLE : View.GONE);
    152     }
    153 
    154     private void addAction(View child) {
    155         mActionsGroup.addView(child, new LinearLayout.LayoutParams(mSize, mSize, 1));
    156     }
    157 
    158     @RequiresApi(21)
    159     private void handleSetRemoteInputActions(final SliceItem input, SliceItem image,
    160             final SliceItem action) {
    161         if (input.getRemoteInput().getAllowFreeFormInput()) {
    162             boolean tint = !image.hasHint(HINT_NO_TINT);
    163             addAction(image.getIcon(), tint).setOnClickListener(
    164                     new OnClickListener() {
    165                         @Override
    166                         public void onClick(View v) {
    167                             handleRemoteInputClick(v, action,
    168                                     input.getRemoteInput());
    169                         }
    170                     });
    171             createRemoteInputView(mColor, getContext());
    172         }
    173     }
    174 
    175     @RequiresApi(21)
    176     private void createRemoteInputView(int color, Context context) {
    177         View riv = RemoteInputView.inflate(context, this);
    178         riv.setVisibility(View.INVISIBLE);
    179         addView(riv, new LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT));
    180         riv.setBackgroundColor(color);
    181     }
    182 
    183     @RequiresApi(21)
    184     private boolean handleRemoteInputClick(View view, SliceItem action,
    185             RemoteInput input) {
    186         if (input == null) {
    187             return false;
    188         }
    189 
    190         ViewParent p = view.getParent().getParent();
    191         RemoteInputView riv = null;
    192         while (p != null) {
    193             if (p instanceof View) {
    194                 View pv = (View) p;
    195                 riv = findRemoteInputView(pv);
    196                 if (riv != null) {
    197                     break;
    198                 }
    199             }
    200             p = p.getParent();
    201         }
    202         if (riv == null) {
    203             return false;
    204         }
    205 
    206         int width = view.getWidth();
    207         if (view instanceof TextView) {
    208             // Center the reveal on the text which might be off-center from the TextView
    209             TextView tv = (TextView) view;
    210             if (tv.getLayout() != null) {
    211                 int innerWidth = (int) tv.getLayout().getLineWidth(0);
    212                 innerWidth += tv.getCompoundPaddingLeft() + tv.getCompoundPaddingRight();
    213                 width = Math.min(width, innerWidth);
    214             }
    215         }
    216         int cx = view.getLeft() + width / 2;
    217         int cy = view.getTop() + view.getHeight() / 2;
    218         int w = riv.getWidth();
    219         int h = riv.getHeight();
    220         int r = Math.max(
    221                 Math.max(cx + cy, cx + (h - cy)),
    222                 Math.max((w - cx) + cy, (w - cx) + (h - cy)));
    223 
    224         riv.setRevealParameters(cx, cy, r);
    225         riv.setAction(action);
    226         riv.setRemoteInput(new RemoteInput[] {
    227                 input
    228         }, input);
    229         riv.focusAnimated();
    230         return true;
    231     }
    232 
    233     @RequiresApi(21)
    234     private RemoteInputView findRemoteInputView(View v) {
    235         if (v == null) {
    236             return null;
    237         }
    238         return (RemoteInputView) v.findViewWithTag(RemoteInputView.VIEW_TAG);
    239     }
    240 }
    241