ActionRow.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.HINT_NO_TINT;
import static android.app.slice.SliceItem.FORMAT_IMAGE;
import static android.app.slice.SliceItem.FORMAT_REMOTE_INPUT;

import static androidx.slice.core.SliceHints.ICON_IMAGE;

import android.app.PendingIntent.CanceledException;
import android.app.RemoteInput;
import android.app.slice.Slice;
import android.content.Context;
import android.content.res.ColorStateList;
import android.graphics.Color;
import android.os.Build;
import android.util.Log;
import android.util.TypedValue;
import android.view.View;
import android.view.ViewParent;
import android.widget.FrameLayout;
import android.widget.ImageView;
import android.widget.ImageView.ScaleType;
import android.widget.LinearLayout;
import android.widget.TextView;

import androidx.annotation.NonNull;
import androidx.annotation.RequiresApi;
import androidx.annotation.RestrictTo;
import androidx.core.graphics.drawable.IconCompat;
import androidx.core.widget.ImageViewCompat;
import androidx.slice.SliceItem;
import androidx.slice.core.SliceActionImpl;
import androidx.slice.core.SliceQuery;

import java.util.List;

/**
 * @hide
 */
@RestrictTo(RestrictTo.Scope.LIBRARY)
public class ActionRow extends FrameLayout {

    private static final int MAX_ACTIONS = 5;
    private static final String TAG = "ActionRow";

    private final int mSize;
    private final int mIconPadding;
    private final LinearLayout mActionsGroup;
    private final boolean mFullActions;
    private int mColor = Color.BLACK;

    public ActionRow(Context context, boolean fullActions) {
        super(context);
        mFullActions = fullActions;
        mSize = (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 48,
                context.getResources().getDisplayMetrics());
        mIconPadding = (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 12,
                context.getResources().getDisplayMetrics());
        mActionsGroup = new LinearLayout(context);
        mActionsGroup.setOrientation(LinearLayout.HORIZONTAL);
        mActionsGroup.setLayoutParams(
                new LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.WRAP_CONTENT));
        addView(mActionsGroup);
    }

    private void setColor(int color) {
        mColor = color;
        for (int i = 0; i < mActionsGroup.getChildCount(); i++) {
            View view = mActionsGroup.getChildAt(i);
            int mode = (Integer) view.getTag();
            boolean tint = mode == ICON_IMAGE;
            if (tint) {
                ImageViewCompat.setImageTintList((ImageView) view, ColorStateList.valueOf(mColor));
            }
        }
    }

    private ImageView addAction(IconCompat icon, boolean allowTint) {
        ImageView imageView = new ImageView(getContext());
        imageView.setPadding(mIconPadding, mIconPadding, mIconPadding, mIconPadding);
        imageView.setScaleType(ScaleType.FIT_CENTER);
        imageView.setImageDrawable(icon.loadDrawable(getContext()));
        if (allowTint) {
            ImageViewCompat.setImageTintList(imageView, ColorStateList.valueOf(mColor));
        }
        imageView.setBackground(SliceViewUtil.getDrawable(getContext(),
                android.R.attr.selectableItemBackground));
        imageView.setTag(allowTint);
        addAction(imageView);
        return imageView;
    }

    /**
     * Set the actions and color for this action row.
     */
    public void setActions(@NonNull List<SliceItem> actions, int color) {
        removeAllViews();
        mActionsGroup.removeAllViews();
        addView(mActionsGroup);
        if (color != -1) {
            setColor(color);
        }
        for (final SliceItem action : actions) {
            if (mActionsGroup.getChildCount() >= MAX_ACTIONS) {
                return;
            }
            final SliceItem input = SliceQuery.find(action, FORMAT_REMOTE_INPUT);
            final SliceItem image = SliceQuery.find(action, FORMAT_IMAGE);
            if (input != null && image != null) {
                if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
                    handleSetRemoteInputActions(input, image, action);
                } else {
                    Log.w(TAG, "Received RemoteInput on API <20 " + input);
                }
            } else if (action.hasHint(Slice.HINT_SHORTCUT)) {
                final SliceActionImpl ac = new SliceActionImpl(action);
                IconCompat iconItem = ac.getIcon();
                if (iconItem != null && ac.getActionItem() != null) {
                    boolean tint = ac.getImageMode() == ICON_IMAGE;
                    addAction(iconItem, tint).setOnClickListener(
                            new OnClickListener() {
                                @Override
                                public void onClick(View v) {
                                    try {
                                        // TODO - should log events here
                                        ac.getActionItem().fireAction(null, null);
                                    } catch (CanceledException e) {
                                        e.printStackTrace();
                                    }
                                }
                            });
                }
            }
        }
        setVisibility(getChildCount() != 0 ? View.VISIBLE : View.GONE);
    }

    private void addAction(View child) {
        mActionsGroup.addView(child, new LinearLayout.LayoutParams(mSize, mSize, 1));
    }

    @RequiresApi(21)
    private void handleSetRemoteInputActions(final SliceItem input, SliceItem image,
            final SliceItem action) {
        if (input.getRemoteInput().getAllowFreeFormInput()) {
            boolean tint = !image.hasHint(HINT_NO_TINT);
            addAction(image.getIcon(), tint).setOnClickListener(
                    new OnClickListener() {
                        @Override
                        public void onClick(View v) {
                            handleRemoteInputClick(v, action,
                                    input.getRemoteInput());
                        }
                    });
            createRemoteInputView(mColor, getContext());
        }
    }

    @RequiresApi(21)
    private void createRemoteInputView(int color, Context context) {
        View riv = RemoteInputView.inflate(context, this);
        riv.setVisibility(View.INVISIBLE);
        addView(riv, new LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT));
        riv.setBackgroundColor(color);
    }

    @RequiresApi(21)
    private boolean handleRemoteInputClick(View view, SliceItem action,
            RemoteInput input) {
        if (input == null) {
            return false;
        }

        ViewParent p = view.getParent().getParent();
        RemoteInputView riv = null;
        while (p != null) {
            if (p instanceof View) {
                View pv = (View) p;
                riv = findRemoteInputView(pv);
                if (riv != null) {
                    break;
                }
            }
            p = p.getParent();
        }
        if (riv == null) {
            return false;
        }

        int width = view.getWidth();
        if (view instanceof TextView) {
            // Center the reveal on the text which might be off-center from the TextView
            TextView tv = (TextView) view;
            if (tv.getLayout() != null) {
                int innerWidth = (int) tv.getLayout().getLineWidth(0);
                innerWidth += tv.getCompoundPaddingLeft() + tv.getCompoundPaddingRight();
                width = Math.min(width, innerWidth);
            }
        }
        int cx = view.getLeft() + width / 2;
        int cy = view.getTop() + view.getHeight() / 2;
        int w = riv.getWidth();
        int h = riv.getHeight();
        int r = Math.max(
                Math.max(cx + cy, cx + (h - cy)),
                Math.max((w - cx) + cy, (w - cx) + (h - cy)));

        riv.setRevealParameters(cx, cy, r);
        riv.setAction(action);
        riv.setRemoteInput(new RemoteInput[] {
                input
        }, input);
        riv.focusAnimated();
        return true;
    }

    @RequiresApi(21)
    private RemoteInputView findRemoteInputView(View v) {
        if (v == null) {
            return null;
        }
        return (RemoteInputView) v.findViewWithTag(RemoteInputView.VIEW_TAG);
    }
}