package tk.wasdennnoch.androidn_ify.systemui.notifications;
import android.annotation.SuppressLint;
import android.content.Context;
import android.view.Gravity;
import android.view.MotionEvent;
import android.view.View;
import android.view.ViewGroup;
import android.widget.FrameLayout;
import android.widget.Toast;
import java.util.ArrayList;
import java.util.List;
import de.robv.android.xposed.XC_MethodHook;
import de.robv.android.xposed.XC_MethodReplacement;
import de.robv.android.xposed.XposedHelpers;
import tk.wasdennnoch.androidn_ify.R;
import tk.wasdennnoch.androidn_ify.XposedHook;
import tk.wasdennnoch.androidn_ify.extracted.systemui.ExpandableIndicator;
import tk.wasdennnoch.androidn_ify.misc.SafeOnClickListener;
import tk.wasdennnoch.androidn_ify.systemui.qs.QSContainerHelper;
import tk.wasdennnoch.androidn_ify.systemui.qs.customize.QSCustomizer;
import tk.wasdennnoch.androidn_ify.utils.ConfigUtils;
@SuppressLint("StaticFieldLeak")
public class NotificationPanelHooks {
private static final String TAG = "NotificationPanelHooks";
private static final String PACKAGE_SYSTEMUI = XposedHook.PACKAGE_SYSTEMUI;
private static final String CLASS_NOTIFICATION_STACK_SCROLL_LAYOUT = "com.android.systemui.statusbar.stack.NotificationStackScrollLayout";
private static final String CLASS_NOTIFICATION_PANEL_VIEW = "com.android.systemui.statusbar.phone.NotificationPanelView";
private static final String CLASS_QS_CONTAINER = "com.android.systemui.qs.QSContainer";
private static final String CLASS_PANEL_VIEW = "com.android.systemui.statusbar.phone.PanelView";
public static final int STATE_KEYGUARD = 1;
private static ViewGroup mNotificationPanelView;
private static ExpandableIndicator mExpandIndicator;
private static QSCustomizer mQsCustomizer;
private static QSContainerHelper mQsContainerHelper;
private static final List<BarStateCallback> mBarStateCallbacks = new ArrayList<>();
private static final XC_MethodHook onFinishInflateHook = new XC_MethodHook() {
@Override
protected void afterHookedMethod(MethodHookParam param) throws Throwable {
mNotificationPanelView = (ViewGroup) param.thisObject;
Context context = mNotificationPanelView.getContext();
mNotificationPanelView.setClipChildren(false);
mNotificationPanelView.setClipToPadding(false);
View mHeader = (View) XposedHelpers.getObjectField(param.thisObject, "mHeader");
mHeader.setOnClickListener(null);
mExpandIndicator = (ExpandableIndicator) mHeader.findViewById(R.id.statusbar_header_expand_indicator);
mExpandIndicator.setOnClickListener(mExpandIndicatorListener);
if (!ConfigUtils.qs().keep_qs_panel_background) {
View mQsContainer = (View) XposedHelpers.getObjectField(param.thisObject, "mQsContainer");
try {
//noinspection deprecation
mQsContainer.setBackgroundColor(context.getResources().getColor(context.getResources().getIdentifier("system_primary_color", "color", PACKAGE_SYSTEMUI)));
} catch (Throwable t) {
XposedHook.logE(TAG, "Couldn't change QS container background color", t);
}
}
FrameLayout.LayoutParams qsCustomizerLp = new FrameLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT);
qsCustomizerLp.gravity = Gravity.CENTER_HORIZONTAL;
QSCustomizer qsCustomizer = new QSCustomizer(context);
mNotificationPanelView.addView(qsCustomizer, qsCustomizerLp);
mQsCustomizer = qsCustomizer;
if (ConfigUtils.qs().fix_header_space) {
mQsContainerHelper = new QSContainerHelper(mNotificationPanelView,
(ViewGroup) XposedHelpers.getObjectField(mNotificationPanelView, "mQsContainer"),
(ViewGroup) XposedHelpers.getObjectField(mNotificationPanelView, "mHeader"),
(ViewGroup) XposedHelpers.getObjectField(mNotificationPanelView, "mQsPanel"));
mNotificationPanelView.requestLayout();
}
}
};
private static final XC_MethodHook setBarStateHook = new XC_MethodHook() {
@Override
protected void afterHookedMethod(MethodHookParam param) throws Throwable {
XposedHook.logD(TAG, "setBarStateHook: Setting state to " + (int) param.args[0]);
//StatusBarHeaderHooks.onSetBarState((int) param.args[0]);
for (BarStateCallback callback : mBarStateCallbacks) {
callback.onStateChanged();
}
}
};
private static XC_MethodHook setVerticalPanelTranslationHook = new XC_MethodHook() {
@Override
protected void afterHookedMethod(MethodHookParam param) throws Throwable {
if (mQsCustomizer != null)
mQsCustomizer.setTranslationX((float) param.args[0]);
}
};
private static final View.OnClickListener mExpandIndicatorListener = new SafeOnClickListener() {
@Override
public void onClickSafe(View v) {
// Fixes an issue with the indicator having two backgrounds when layer type is hardware
mExpandIndicator.setLayerType(View.LAYER_TYPE_NONE, null);
flingSettings(!mExpandIndicator.isExpanded());
}
};
private static Runnable mRunAfterInstantCollapse;
private static Runnable mInstantCollapseRunnable = new Runnable() {
@Override
public void run() {
instantCollapse();
if (mRunAfterInstantCollapse != null)
mNotificationPanelView.post(mRunAfterInstantCollapse);
}
};
public static void expandWithQs() {
try {
if (ConfigUtils.M) {
XposedHelpers.callMethod(mNotificationPanelView, "expandWithQs");
} else {
XposedHelpers.callMethod(mNotificationPanelView, "expand");
}
} catch (Throwable ignore) {
}
}
public static boolean isExpanded() {
return (mExpandIndicator != null && mExpandIndicator.isExpanded());
}
public static boolean isCollapsed() {
return (mExpandIndicator != null && !mExpandIndicator.isExpanded());
}
public static void expandIfNecessary() {
if (mExpandIndicator != null && mNotificationPanelView != null) {
if (!mExpandIndicator.isExpanded()) {
flingSettings(true);
}
}
}
public static void collapseIfNecessary() {
if (mExpandIndicator != null && mNotificationPanelView != null) {
if (mExpandIndicator.isExpanded()) {
flingSettings(false);
}
}
}
private static void flingSettings(boolean expanded) {
try {
XposedHelpers.callMethod(mNotificationPanelView, "flingSettings", new Class[]{float.class, boolean.class, Runnable.class, boolean.class}, 0, expanded, null, true);
} catch (Throwable t) {
XposedHelpers.callMethod(mNotificationPanelView, "flingSettings", 0, expanded);
}
}
public static int getStatusBarState() {
if (mNotificationPanelView == null) {
return 0;
}
return XposedHelpers.getIntField(mNotificationPanelView, "mStatusBarState");
}
public static void hook(ClassLoader classLoader) {
try {
if (ConfigUtils.qs().header) { // Although this is the notification panel everything here is header-related (mainly QS editor)
Class<?> classNotificationStackScrollLayout = XposedHelpers.findClass(CLASS_NOTIFICATION_STACK_SCROLL_LAYOUT, classLoader);
Class<?> classNotificationPanelView = XposedHelpers.findClass(CLASS_NOTIFICATION_PANEL_VIEW, classLoader);
Class<?> classQSContainer = XposedHelpers.findClass(CLASS_QS_CONTAINER, classLoader);
Class<?> classPanelView = XposedHelpers.findClass(CLASS_PANEL_VIEW, classLoader);
XposedHelpers.findAndHookMethod(classNotificationPanelView, "onFinishInflate", onFinishInflateHook);
XposedHelpers.findAndHookMethod(classNotificationPanelView, "setBarState", int.class, boolean.class, boolean.class, setBarStateHook);
if (ConfigUtils.M)
XposedHelpers.findAndHookMethod(classNotificationPanelView, "setVerticalPanelTranslation", float.class, setVerticalPanelTranslationHook);
XposedHelpers.findAndHookMethod(classPanelView, "schedulePeek", new XC_MethodHook() {
@Override
protected void afterHookedMethod(MethodHookParam param) throws Throwable {
XposedHelpers.callMethod(mNotificationPanelView, "setListening", true);
}
});
XC_MethodHook returnIfCustomizing = new XC_MethodHook() {
@Override
protected void beforeHookedMethod(MethodHookParam param) throws Throwable {
if (mQsCustomizer != null && mQsCustomizer.isCustomizing())
param.setResult(false);
}
};
XposedHelpers.findAndHookMethod(classNotificationPanelView, "onInterceptTouchEvent", MotionEvent.class, returnIfCustomizing);
XposedHelpers.findAndHookMethod(classNotificationPanelView, "onTouchEvent", MotionEvent.class, returnIfCustomizing);
if (ConfigUtils.qs().fix_header_space) {
XC_MethodReplacement updateQsTranslation = new XC_MethodReplacement() {
@Override
protected Object replaceHookedMethod(MethodHookParam param) throws Throwable {
if (mQsContainerHelper != null)
mQsContainerHelper.setQsExpansion((float) XposedHelpers.callMethod(param.thisObject, "getQsExpansionFraction"),
(float) XposedHelpers.callMethod(param.thisObject, "getHeaderTranslation"));
return null;
}
};
XposedHelpers.findAndHookMethod(classNotificationPanelView, "setQsTranslation", float.class, updateQsTranslation);
XposedHelpers.findAndHookMethod(classQSContainer, "getDesiredHeight", new XC_MethodReplacement() {
@Override
protected Object replaceHookedMethod(MethodHookParam param) throws Throwable {
if (mQsContainerHelper != null)
return mQsContainerHelper.getDesiredHeight();
return 0;
}
});
XposedHelpers.findAndHookMethod(classQSContainer, "updateBottom", new XC_MethodReplacement() {
@Override
protected Object replaceHookedMethod(MethodHookParam param) throws Throwable {
if (mQsContainerHelper != null)
mQsContainerHelper.updateBottom();
return null;
}
});
XposedHelpers.findAndHookMethod(classNotificationPanelView, "isQsDetailShowing", new XC_MethodReplacement() {
@Override
protected Object replaceHookedMethod(MethodHookParam param) throws Throwable {
if (mQsContainerHelper != null)
return mQsContainerHelper.getQSDetail().isShowingDetail();
return null;
}
});
hookOnLayout(classNotificationPanelView, classPanelView);
/*
XposedHelpers.findAndHookMethod(classNotificationPanelView, "animateHeaderSlidingIn", new XC_MethodReplacement() {
@Override
protected Object replaceHookedMethod(MethodHookParam param) throws Throwable {
if (mQsContainerHelper != null)
mQsContainerHelper.animateHeaderSlidingIn();
return null;
}
});
XposedHelpers.findAndHookMethod(classNotificationPanelView, "animateHeaderSlidingOut", new XC_MethodReplacement() {
@Override
protected Object replaceHookedMethod(MethodHookParam param) throws Throwable {
if (mQsContainerHelper != null)
mQsContainerHelper.animateHeaderSlidingOut();
return null;
}
});
XposedHelpers.findAndHookMethod(classNotificationPanelView, "updateHeaderShade", updateQsTranslation);
XposedHelpers.findAndHookMethod(classNotificationStackScrollLayout, "onScrollTouch", MotionEvent.class, new XC_MethodHook() {
@Override
protected void beforeHookedMethod(MethodHookParam param) throws Throwable {
MotionEvent ev = (MotionEvent) param.args[0];
if (mQsContainerHelper != null && ev.getY() < mQsContainerHelper.getBottom())
param.setResult(false);
}
});
*/
}
}
} catch (Throwable t) {
XposedHook.logE(TAG, "Error in hook", t);
}
}
private static void hookOnLayout(final Class<?> classNotificationPanelView, final Class<?> classPanelView) {
XposedHelpers.findAndHookMethod(classNotificationPanelView, "onLayout", boolean.class, int.class, int.class, int.class, int.class, new XC_MethodReplacement() {
@Override
protected Object replaceHookedMethod(MethodHookParam param) throws Throwable {
if (mQsContainerHelper != null)
mQsContainerHelper.notificationPanelViewOnLayout(param, classPanelView);
return null;
}
});
}
public static void postInstantCollapse(Runnable after) {
mRunAfterInstantCollapse = after;
mNotificationPanelView.post(mInstantCollapseRunnable);
}
private static void instantCollapse() {
XposedHelpers.callMethod(mNotificationPanelView, "instantCollapse");
}
public static void addBarStateCallback(BarStateCallback callback) {
mBarStateCallbacks.add(callback);
}
public static void removeBarStateCallback(BarStateCallback callback) {
mBarStateCallbacks.remove(callback);
}
static QSCustomizer getQsCustomizer() {
return mQsCustomizer;
}
public interface BarStateCallback {
void onStateChanged();
}
public static void invalidateTileAdapter() {
if (mQsCustomizer != null)
mQsCustomizer.invalidateTileAdapter();
}
public static void handleStateChanged(Object qsTile, Object state) {
if (mQsCustomizer != null)
mQsCustomizer.handleStateChanged(qsTile, state);
}
public static void showQsCustomizer(ArrayList<Object> records, boolean animated) {
if (canShowCustomizer(records))
mQsCustomizer.show(records, animated);
}
public static void showQsCustomizer(ArrayList<Object> records, int x, int y) {
if (canShowCustomizer(records))
mQsCustomizer.show(records, x, y);
}
private static boolean canShowCustomizer(ArrayList<Object> records) {
if (records == null) {
Toast.makeText(StatusBarHeaderHooks.mContext, "Couldn't open edit view; mRecords == null", Toast.LENGTH_SHORT).show();
XposedHook.logE(TAG, "Couldn't open edit view; mRecords == null", null);
return false;
}
if (mQsCustomizer == null) {
Toast.makeText(StatusBarHeaderHooks.mContext, "Couldn't open edit view; mQsCustomizer == null", Toast.LENGTH_SHORT).show();
XposedHook.logE(TAG, "Couldn't open edit view; mQsCustomizer == null", null);
return false;
}
return true;
}
}