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.util.AttributeSet; import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; import android.widget.CompoundButton; import android.widget.ImageView; import android.widget.TextView; import androidx.annotation.Nullable; import androidx.core.content.ContextCompat; import androidx.core.graphics.drawable.DrawableCompat; 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 boolean isHasBackground(View view) { if (view instanceof ImageView) { ImageView image = (ImageView) view; return image.getBackground() != null || image.getDrawable() != null; } if (view instanceof TextView) { TextView text = (TextView) view; return text.getBackground() != null; } if (view instanceof ViewGroup) { ViewGroup group = (ViewGroup) view; int count = group.getChildCount(); for (int i = 0; i < count; i++) { View child = group.getChildAt(i); if (isHasBackground(child)) { return true; } } } return view.getBackground() != null; } @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; } if (isHasBackground(view)) { Object tag = view.getTag(R.id.click_pressed_attr_id); checkSetBgIfNeed(null, view, tag == null ? null : (AttributeSet) tag, true); } else if (view instanceof TextView) { Object tag = view.getTag(R.id.click_pressed_attr_id); checkSetBgIfNeed(null, (TextView) view, tag == null ? null : (AttributeSet) tag, true); } view.setOnClickListener(listener); } @TargetClass(value = "android.view.View", scope = Scope.ALL) @TargetMethod(methodName = "getBackground") @ReplaceInvoke public static Drawable getBackgroundDrawable(View view) { Object tag = view.getTag(R.id.click_pressed_attr_replaced); Drawable old = view.getBackground(); if (tag == null) { return old; } return (Drawable) tag; } @TargetClass(value = "android.widget.ImageView", scope = Scope.ALL) @TargetMethod(methodName = "getDrawable") @ReplaceInvoke public static Drawable getDrawable(ImageView view) { Object tag = view.getTag(R.id.click_pressed_attr_replaced); Drawable old = view.getDrawable(); if (tag == null) { return old; } return (Drawable) tag; } @TargetClass(value = "android.widget.TextView", scope = Scope.ALL) @TargetMethod(methodName = "getTextColors") @ReplaceInvoke public static ColorStateList getTextColors(TextView view) { Object tag = view.getTag(R.id.click_pressed_attr_replaced_color); ColorStateList old = view.getTextColors(); if (tag == null) { return old; } return (ColorStateList) tag; } @TargetClass(value = "android.widget.CompoundButton", scope = Scope.ALL) @TargetMethod(methodName = "setOnCheckedChangeListener") @ReplaceInvoke public static void setOnCheckedChangeListener(CompoundButton view, CompoundButton.OnCheckedChangeListener listener) { if (view == null) { return; } if (listener == null) { view.setOnCheckedChangeListener(null); return; } if (isHasBackground(view)) { Object tag = view.getTag(R.id.click_pressed_attr_id); checkSetBgIfNeed(null, view, tag == null ? null : (AttributeSet) tag, true); } view.setOnCheckedChangeListener(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; } if (isHasBackground(view)) { Object tag = view.getTag(R.id.click_pressed_attr_id); checkSetBgIfNeed(null, view, tag == null ? null : (AttributeSet) tag, true); } else if (view instanceof TextView) { Object tag = view.getTag(R.id.click_pressed_attr_id); checkSetBgIfNeed(null, (TextView) view, tag == null ? null : (AttributeSet) tag, true); } view.setOnLongClickListener(listener); } @TargetClass(value = "android.view.View", scope = Scope.ALL) @TargetMethod(methodName = "setBackgroundDrawable") @ReplaceInvoke public static void setBackgroundDrawable(View view, Drawable drawable) { if (view == null) { return; } if (!view.isLongClickable() && !view.isClickable()) { view.setBackground(drawable); return; } float alpha = getAlpha(view); if (alpha >= 0) { Drawable replaced = checkAndReplaceDrawable(drawable, alpha); if (replaced != null) { view.setTag(R.id.click_pressed_attr_replaced, drawable); int paddingLeft = view.getPaddingLeft(); int paddingRight = view.getPaddingRight(); int paddingTop = view.getPaddingTop(); int paddingBottom = view.getPaddingBottom(); view.setBackground(replaced); view.setPadding(paddingLeft, paddingTop, paddingRight, paddingBottom); } else { view.setBackground(drawable); } } else { view.setBackground(drawable); } } @TargetClass(value = "android.view.View", scope = Scope.ALL) @TargetMethod(methodName = "setBackground") @ReplaceInvoke public static void setBackground(View view, Drawable drawable) { if (view == null) { return; } if (!view.isLongClickable() && !view.isClickable()) { view.setBackground(drawable); return; } float alpha = getAlpha(view); if (alpha >= 0) { Drawable replaced = checkAndReplaceDrawable(drawable, alpha); if (replaced != null) { view.setTag(R.id.click_pressed_attr_replaced, drawable); int paddingLeft = view.getPaddingLeft(); int paddingRight = view.getPaddingRight(); int paddingTop = view.getPaddingTop(); int paddingBottom = view.getPaddingBottom(); view.setBackground(replaced); view.setPadding(paddingLeft, paddingTop, paddingRight, paddingBottom); } else { view.setBackground(drawable); } } else { view.setBackground(drawable); } } @TargetClass(value = "android.widget.ImageView", scope = Scope.ALL) @TargetMethod(methodName = "setImageDrawable") @ReplaceInvoke public static void setImageViewDrawable(ImageView view, Drawable drawable) { if (view == null) { return; } if (!view.isLongClickable() && !view.isClickable()) { view.setImageDrawable(drawable); return; } float alpha = getAlpha(view); if (alpha >= 0.0) { Drawable replaced = checkAndReplaceDrawable(drawable, alpha); if (replaced != null) { view.setTag(R.id.click_pressed_attr_replaced, drawable); int paddingLeft = view.getPaddingLeft(); int paddingRight = view.getPaddingRight(); int paddingTop = view.getPaddingTop(); int paddingBottom = view.getPaddingBottom(); view.setImageDrawable(replaced); view.setPadding(paddingLeft, paddingTop, paddingRight, paddingBottom); } else { view.setImageDrawable(drawable); } } else { view.setImageDrawable(drawable); } } @TargetClass(value = "android.widget.ImageView", scope = Scope.ALL) @TargetMethod(methodName = "setImageResource") @ReplaceInvoke public static void setImageViewResource(ImageView view, int resId) { if (view == null) { return; } if (!view.isLongClickable() && !view.isClickable()) { view.setImageResource(resId); return; } float alpha = getAlpha(view); if (alpha >= 0.0) { Drawable drawable = ContextCompat.getDrawable(view.getContext(), resId); Drawable replaced = checkAndReplaceDrawable(drawable, alpha); if (replaced != null) { view.setTag(R.id.click_pressed_attr_replaced, drawable); int paddingLeft = view.getPaddingLeft(); int paddingRight = view.getPaddingRight(); int paddingTop = view.getPaddingTop(); int paddingBottom = view.getPaddingBottom(); view.setImageDrawable(replaced); view.setPadding(paddingLeft, paddingTop, paddingRight, paddingBottom); } else { view.setImageDrawable(drawable); } } else { view.setImageResource(resId); } } public static float getAlpha(View view) { Object tag = view.getTag(R.id.click_pressed_attr_enabled); if (tag != null && !((Boolean) tag)) { return -1.0f; } tag = view.getTag(R.id.click_pressed_attr_id); if (tag instanceof AttributeSet) { AttributeSet attr = (AttributeSet) tag; TypedArray array = null; try { array = view.getContext().obtainStyledAttributes(attr, R.styleable.ClickPressedStyle); return array.getFloat(R.styleable.ClickPressedStyle_pressed_alpha, 0.6f); } finally { if (array != null) { array.recycle(); } } } return -1.0f; } @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, false); 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, false); 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, boolean check) { 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]"); } } if (!check) { view.setTag(R.id.click_pressed_attr_id, attr); return; } } } finally { if (t != null) { t.recycle(); } } boolean rst = check(view, alpha); if (!rst && view != null) { view.setTag(R.id.click_pressed_attr_enabled, false); } } catch (Throwable t) { t.printStackTrace(); } } private static boolean check(View view, float alpha) { if (view instanceof ImageView) { ImageView image = (ImageView) view; Drawable old = image.getBackground(); Drawable replaced = checkAndReplaceDrawable(old, alpha); if (replaced != null) { view.setTag(R.id.click_pressed_attr_replaced, old); image.setBackground(replaced); return true; } old = image.getDrawable(); replaced = checkAndReplaceDrawable(old, alpha); if (replaced != null) { view.setTag(R.id.click_pressed_attr_replaced, old); int paddingLeft = view.getPaddingLeft(); int paddingRight = view.getPaddingRight(); int paddingTop = view.getPaddingTop(); int paddingBottom = view.getPaddingBottom(); image.setImageDrawable(replaced); image.setPadding(paddingLeft, paddingTop, paddingRight, paddingBottom); return true; } return false; } if (view instanceof TextView) { TextView text = (TextView) view; Drawable background = text.getBackground(); Drawable replaced = checkAndReplaceDrawable(background, alpha); if (replaced != null) { view.setTag(R.id.click_pressed_attr_replaced, background); int paddingLeft = text.getPaddingLeft(); int paddingRight = text.getPaddingRight(); int paddingTop = text.getPaddingTop(); int paddingBottom = text.getPaddingBottom(); text.setBackground(replaced); text.setPadding(paddingLeft, paddingTop, paddingRight, paddingBottom); return true; } ColorStateList textColor = text.getTextColors(); if (textColor != null) { int current = text.getCurrentTextColor(); int pressed = textColor.getColorForState(new int[]{ android.R.attr.state_pressed }, Integer.MIN_VALUE); if (pressed != Integer.MIN_VALUE && pressed != current) { return false; } int enableColor = textColor.getColorForState(new int[] { android.R.attr.state_enabled }, Integer.MIN_VALUE); int defaultColor = current; if (enableColor == Integer.MIN_VALUE && enableColor != current) { 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 && disableColor != current) { size += 1; } boolean hasChecked = false; int checkedColor = textColor.getColorForState(new int[] { android.R.attr.state_checked }, Integer.MIN_VALUE); if (checkedColor != Integer.MIN_VALUE && checkedColor != current) { hasChecked = true; size += 1; } boolean hasUnChecked = false; int unCheckedColor = textColor.getColorForState(new int[]{ -android.R.attr.state_checked }, Integer.MIN_VALUE); if (unCheckedColor != Integer.MIN_VALUE && unCheckedColor != current) { hasUnChecked = true; 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; if (hasChecked) { states[3] = new int[] { android.R.attr.state_checked }; colors[3] = checkedColor; } if (hasUnChecked) { if (size > 4) { states[4] = new int[] { -android.R.attr.state_checked }; colors[4] = unCheckedColor; } else { states[3] = new int[] { -android.R.attr.state_checked }; colors[3] = unCheckedColor; } } } else { states[1] = new int[] { -android.R.attr.state_pressed }; colors[1] = defaultColor; } ColorStateList newColor = new ColorStateList(states, colors); text.setTag(R.id.click_pressed_attr_replaced_color, textColor); text.setTextColor(newColor); return true; } return false; } if (view != null) { Drawable old = view.getBackground(); Drawable replaced = checkAndReplaceDrawable(old, alpha); if (replaced != null) { view.setTag(R.id.click_pressed_attr_replaced, old); int paddingLeft = view.getPaddingLeft(); int paddingRight = view.getPaddingRight(); int paddingTop = view.getPaddingTop(); int paddingBottom = view.getPaddingBottom(); view.setBackground(replaced); view.setPadding(paddingLeft, paddingTop, paddingRight, paddingBottom); return true; } } if (view instanceof ViewGroup) { ViewGroup group = (ViewGroup) view; int count = group.getChildCount(); for (int i = 0; i < count; i++) { View child = group.getChildAt(i); if (check(child, alpha)) { return true; } } } return false; } 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; for (int state : states) { if (state == android.R.attr.state_pressed) { hasPressed = true; break; } } if (hasPressed) { return null; } Drawable origin = drawable.getCurrent(); 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; } Drawable.ConstantState constantState = old.getConstantState(); if (constantState != null) { StateListDrawable result = new StateListDrawable(); int[] state = { -android.R.attr.state_pressed }; result.addState(state, 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; } }