package com.mogo.launcher.lancet; import android.content.Context; import android.content.res.ColorStateList; import android.content.res.TypedArray; import android.graphics.Color; import android.graphics.drawable.Drawable; import android.graphics.drawable.StateListDrawable; import android.os.Build; import android.util.AttributeSet; import android.util.Log; import android.view.LayoutInflater; import android.view.View; import android.widget.ImageView; import android.widget.TextView; import androidx.annotation.Nullable; import androidx.core.graphics.drawable.DrawableCompat; import androidx.core.view.ViewCompat; import com.knightboost.lancet.api.Origin; import com.knightboost.lancet.api.Scope; import com.knightboost.lancet.api.annotations.Group; import com.knightboost.lancet.api.annotations.ImplementedInterface; import com.knightboost.lancet.api.annotations.Insert; import com.knightboost.lancet.api.annotations.ReplaceInvoke; import com.knightboost.lancet.api.annotations.TargetClass; import com.knightboost.lancet.api.annotations.TargetMethod; import com.knightboost.lancet.api.annotations.Weaver; import com.mogo.launcher.R; @Weaver @Group("view_pressed_state") public class ViewPressedStateLancet { public static class OnClickWrapper implements View.OnClickListener { private final View.OnClickListener delegate; public OnClickWrapper(View.OnClickListener delegate) { this.delegate = delegate; } @Override public void onClick(View v) { Object tag = v.getTag(R.id.click_pressed_attr_id); checkSetBgIfNeed(null, v, tag == null ? null : (AttributeSet) tag); this.delegate.onClick(v); } } public static class OnLongClickWrapper implements View.OnLongClickListener { private final View.OnLongClickListener delegate; public OnLongClickWrapper(View.OnLongClickListener delegate) { this.delegate = delegate; } @Override public boolean onLongClick(View v) { Object tag = v.getTag(R.id.click_pressed_attr_id); checkSetBgIfNeed(null, v, tag == null ? null : (AttributeSet) tag); return delegate.onLongClick(v); } } @TargetClass(value = "android.view.View", scope = Scope.ALL) @TargetMethod(methodName = "setOnClickListener") @ReplaceInvoke public static void setOnClickListener(View view, View.OnClickListener listener) { if (view == null) { return; } if (listener == null) { view.setOnClickListener(null); return; } view.setOnClickListener(new OnClickWrapper(listener)); } @TargetClass(value = "android.view.View", scope = Scope.ALL) @TargetMethod(methodName = "setOnLongClickListener") @ReplaceInvoke public static void setOnLongClickListener(View view, View.OnLongClickListener listener) { if (view == null) { return; } if (listener == null) { view.setOnLongClickListener(null); return; } view.setOnLongClickListener(new OnLongClickWrapper(listener)); } @ImplementedInterface(value = "android.view.LayoutInflater$Factory", scope = Scope.LEAF) @Insert(mayCreateSuper = true) @TargetMethod(methodName = "onCreateView") public View onCreateView(String name, Context context, AttributeSet attrs) { View view = (View) Origin.call(); if (view == null && attrs != null) { view = tryCreateView(name, context, attrs); } checkSetBgIfNeed(null, view, attrs); return view; } @ImplementedInterface(value = "android.view.LayoutInflater$Factory2", scope = Scope.LEAF) @Insert(mayCreateSuper = true) @TargetMethod(methodName = "onCreateView") public View onCreateView(View parent, String name, Context context, AttributeSet attrs) { View view = (View) Origin.call(); if (view == null && attrs != null) { view = tryCreateView(name, context, attrs); } checkSetBgIfNeed(parent, view, attrs); return view; } @Nullable private static View tryCreateView(String name, Context context, AttributeSet attrs) { View ret = null; if (name != null && name.length() > 0) { if (context != null) { try { ret = LayoutInflater.from(context).createView(name, null, attrs); } catch (ClassNotFoundException ignore) {} if (ret == null) { String[] prefixList = { "android.widget.", "android.webkit.", "android.app." }; for (String prefix : prefixList) { try { ret = LayoutInflater.from(context).createView(name, prefix, attrs); } catch (ClassNotFoundException ignore) { } } } } } return ret; } private static void checkSetBgIfNeed(View parent, View view, AttributeSet attr) { try { TypedArray t = null; float alpha = 0.6f; boolean enabled; try { if (parent == null && view == null) { return; } Context context = parent == null ? view.getContext() : parent.getContext(); if (context == null) { return; } if (parent != null) { Object tag = parent.getTag(R.id.click_pressed_attr_enabled); if (tag != null && !((Boolean) tag)) { view.setTag(R.id.click_pressed_attr_enabled, false); return; } } if (view != null) { Object tag = view.getTag(R.id.click_pressed_attr_enabled); if (tag != null && !((Boolean) tag)) { view.setTag(R.id.click_pressed_attr_enabled, false); return; } if (attr != null) { t = context.obtainStyledAttributes(attr, R.styleable.ClickPressedStyle); enabled = t.getBoolean(R.styleable.ClickPressedStyle_pressed_enabled, true); if (!enabled) { view.setTag(R.id.click_pressed_attr_enabled, false); return; } alpha = t.getFloat(R.styleable.ClickPressedStyle_pressed_alpha, 0.6f); if (alpha < 0.0f || alpha > 1.0f) { throw new AssertionError("view custom attr \\'alpha\\' must be in [0.0, 1.0]"); } } view.setTag(R.id.click_pressed_attr_id, attr); } } finally { if (t != null) { t.recycle(); } } if (view instanceof ImageView) { ImageView image = (ImageView) view; Drawable replaced = checkAndReplaceDrawable(image.getBackground(), alpha); if (replaced != null) { ViewCompat.setBackground(image, replaced); return; } replaced = checkAndReplaceDrawable(image.getDrawable(), alpha); if (replaced != null) { image.setImageDrawable(replaced); return; } return; } if (view instanceof TextView) { TextView text = (TextView) view; Drawable replaced = checkAndReplaceDrawable(text.getBackground(), alpha); if (replaced != null) { ViewCompat.setBackground(text, replaced); return; } ColorStateList textColor = text.getTextColors(); if (textColor != null) { int pressed = textColor.getColorForState(new int[]{ android.R.attr.state_pressed }, Integer.MIN_VALUE); if (pressed != Integer.MIN_VALUE) { return; } int color = textColor.getColorForState(new int[] { android.R.attr.state_enabled }, Integer.MIN_VALUE); int defaultColor = color; if (color == Integer.MIN_VALUE) { defaultColor = textColor.getDefaultColor(); } int pressedColor = Color.argb((int)(Color.alpha(defaultColor) * alpha), Color.red(defaultColor), Color.green(defaultColor), Color.blue(defaultColor)); int disableColor = textColor.getColorForState(new int[] { -android.R.attr.state_enabled }, Integer.MIN_VALUE); int size = 2; if (disableColor != Integer.MIN_VALUE) { size += 1; } int[][] states = new int[size][1]; int[] colors = new int[size]; states[0] = new int[] { android.R.attr.state_pressed }; colors[0] = pressedColor; if (size > 2) { states[1] = new int[] { -android.R.attr.state_enabled }; colors[1] = disableColor; states[2] = new int[] { android.R.attr.state_enabled }; colors[2] = defaultColor; } else { states[1] = new int[] { -android.R.attr.state_pressed }; colors[1] = defaultColor; } ColorStateList newColor = new ColorStateList(states, colors); text.setTextColor(newColor); } return; } if (view != null) { Drawable replaced = checkAndReplaceDrawable(view.getBackground(), alpha); if (replaced != null) { ViewCompat.setBackground(view, replaced); } } } catch (Throwable t) { t.printStackTrace(); } } private static Drawable checkAndReplaceDrawable(Drawable old, float alpha) { if (old == null) { return null; } if (old instanceof StateListDrawable) { StateListDrawable drawable = (StateListDrawable) old; int[] states = drawable.getState(); boolean hasPressed = false; int index = 0; int targetIndex = -1; for (int state : states) { if (!hasPressed && state == android.R.attr.state_pressed) { hasPressed = true; } if (targetIndex == -1 && state != -android.R.attr.state_enabled) { targetIndex = index; } if (hasPressed && targetIndex != -1) { break; } index ++; } if (hasPressed) { return null; } if (targetIndex != -1) { Drawable origin= null; if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) { origin = drawable.getStateDrawable(targetIndex); } else { origin = drawable.getCurrent(); } if (origin != null) { Drawable.ConstantState constantState = origin.getConstantState(); if (constantState != null) { Drawable pressed = DrawableCompat.wrap(constantState.newDrawable().mutate()); pressed.setAlpha((int)(255 * alpha)); drawable.addState(new int[] { android.R.attr.state_pressed }, pressed); return drawable; } } return null; } return null; } Drawable.ConstantState constantState = old.getConstantState(); if (constantState != null) { StateListDrawable result = new StateListDrawable(); result.addState(new int[] { -android.R.attr.state_pressed }, old); Drawable pressed = DrawableCompat.wrap(constantState.newDrawable().mutate()); pressed.setAlpha((int)(255 * alpha)); result.addState(new int[] { android.R.attr.state_pressed }, pressed); return result; } return null; } }