package tk.wasdennnoch.androidn_ify.systemui.qs;
import android.content.Context;
import android.util.AttributeSet;
import android.view.View;
import android.view.ViewGroup;
import java.util.Collection;
import java.util.List;
import de.robv.android.xposed.XC_MethodHook;
import de.robv.android.xposed.XC_MethodReplacement;
import de.robv.android.xposed.XposedBridge;
import de.robv.android.xposed.XposedHelpers;
import tk.wasdennnoch.androidn_ify.XposedHook;
import tk.wasdennnoch.androidn_ify.extracted.systemui.qs.PagedTileLayout;
import tk.wasdennnoch.androidn_ify.extracted.systemui.qs.QSDetail;
import tk.wasdennnoch.androidn_ify.systemui.notifications.StatusBarHeaderHooks;
import tk.wasdennnoch.androidn_ify.utils.ConfigUtils;
public class QuickSettingsHooks {
private static final String TAG = "QuickSettingsHooks";
private static final String CLASS_QS_PANEL = "com.android.systemui.qs.QSPanel";
static final String CLASS_QS_DRAG_PANEL = "com.android.systemui.qs.QSDragPanel";
final Class mHookClass;
protected Context mContext;
ViewGroup mQsPanel;
View mBrightnessView;
Object mFooter;
View mDetail;
private PagedTileLayout mTileLayout;
private QSDetail mQSDetail;
private boolean mHookedGetGridHeight = false;
private boolean mExpanded;
private int mGridHeight;
private XC_MethodReplacement
getGridHeightHook = new XC_MethodReplacement() {
@Override
protected Object replaceHookedMethod(MethodHookParam param) throws Throwable {
return mGridHeight;
}
};
public static QuickSettingsHooks create(ClassLoader classLoader) {
try {
XposedHelpers.findClass(CLASS_QS_DRAG_PANEL, classLoader);
return new CMQuickSettingsHooks(classLoader);
} catch (Throwable t) {
return new QuickSettingsHooks(classLoader);
}
}
QuickSettingsHooks(ClassLoader classLoader) {
mHookClass = XposedHelpers.findClass(getHookClass(), classLoader);
hookConstructor();
hookOnMeasure();
hookOnLayout();
hookUpdateResources();
hookSetTiles();
hookSetExpanded();
hookShowDetail();
hookFireScanStateChanged();
}
protected void hookConstructor() {
XposedHelpers.findAndHookConstructor(mHookClass, Context.class, AttributeSet.class, new XC_MethodHook() {
@Override
protected void afterHookedMethod(MethodHookParam param) throws Throwable {
mQsPanel = (ViewGroup) param.thisObject;
mContext = mQsPanel.getContext();
mBrightnessView = (View) XposedHelpers.getObjectField(param.thisObject, "mBrightnessView");
mFooter = XposedHelpers.getObjectField(param.thisObject, "mFooter");
mDetail = (View) XposedHelpers.getObjectField(param.thisObject, "mDetail");
setupTileLayout();
}
});
}
private void hookUpdateResources() {
XposedHelpers.findAndHookMethod(mHookClass, "updateResources", new XC_MethodHook() {
@Override
protected void afterHookedMethod(MethodHookParam param) throws Throwable {
if (mTileLayout != null) {
if (mTileLayout.updateResources())
StatusBarHeaderHooks.postSetupAnimators();
try {
mTileLayout.setColumnCount(XposedHelpers.getIntField(param.thisObject, "mColumns"));
} catch (Throwable ignore) {
}
}
}
});
}
private void hookSetTiles() {
XposedHelpers.findAndHookMethod(mHookClass, "setTiles", Collection.class, new XC_MethodHook() {
@SuppressWarnings("unchecked")
@Override
protected void beforeHookedMethod(MethodHookParam param) throws Throwable {
List<Object> mRecords = (List<Object>) XposedHelpers.getObjectField(param.thisObject, "mRecords");
for (Object record : mRecords) {
mTileLayout.removeTile(record);
}
mTileLayout.removeAll();
}
@SuppressWarnings("unchecked")
@Override
protected void afterHookedMethod(MethodHookParam param) throws Throwable {
List<Object> mRecords = (List<Object>) XposedHelpers.getObjectField(param.thisObject, "mRecords");
for (Object record : mRecords) {
mTileLayout.addTile(record);
}
}
});
}
private void hookSetExpanded() {
XposedHelpers.findAndHookMethod(mHookClass, "setExpanded", boolean.class, new XC_MethodHook() {
@Override
protected void beforeHookedMethod(MethodHookParam param) throws Throwable {
boolean expanded = (boolean) param.args[0];
if (mExpanded == expanded) return;
mExpanded = expanded;
if (!mExpanded) {
mTileLayout.setCurrentItem(0, false);
}
}
});
}
private void hookShowDetail() {
if (!ConfigUtils.qs().fix_header_space) return;
XposedBridge.hookAllMethods(mHookClass, "handleShowDetailImpl", new XC_MethodReplacement() {
@Override
protected Object replaceHookedMethod(MethodHookParam param) throws Throwable {
if (mQSDetail != null)
mQSDetail.handleShowingDetail(param.args[0], (boolean) param.args[1], (int) param.args[2], (int) param.args[3]);
return null;
}
});
}
private void hookFireScanStateChanged() {
if (!ConfigUtils.qs().fix_header_space) return;
XposedHelpers.findAndHookMethod(mHookClass, "fireScanStateChanged", boolean.class, new XC_MethodReplacement() {
@Override
protected Object replaceHookedMethod(MethodHookParam param) throws Throwable {
if (mQSDetail != null)
mQSDetail.onScanStateChanged((boolean) param.args[0]);
return null;
}
});
}
void setupTileLayout() {
mTileLayout = new PagedTileLayout(mContext, null);
mQsPanel.addView(mTileLayout);
}
private void hookOnMeasure() {
XposedHelpers.findAndHookMethod(mHookClass, "onMeasure", int.class, int.class, new XC_MethodReplacement() {
@Override
protected Object replaceHookedMethod(MethodHookParam param) throws Throwable {
onMeasure((int) param.args[0], (int) param.args[1]);
return null;
}
});
}
private void hookOnLayout() {
XposedHelpers.findAndHookMethod(mHookClass, "onLayout", boolean.class, int.class, int.class, int.class, int.class, new XC_MethodReplacement() {
@Override
protected Object replaceHookedMethod(MethodHookParam param) throws Throwable {
onLayout();
return null;
}
});
}
@SuppressWarnings("UnusedParameters")
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
final int width = View.MeasureSpec.getSize(widthMeasureSpec);
mBrightnessView.measure(exactly(width), View.MeasureSpec.UNSPECIFIED);
mTileLayout.measure(exactly(width), View.MeasureSpec.UNSPECIFIED);
View footerView = (View) XposedHelpers.callMethod(mFooter, "getView");
footerView.measure(exactly(width), View.MeasureSpec.UNSPECIFIED);
int h = mBrightnessView.getMeasuredHeight() + mTileLayout.getMeasuredHeight()/* + mPanelPaddingBottom*/;
if ((boolean) XposedHelpers.callMethod(mFooter, "hasFooter")) {
h += footerView.getMeasuredHeight();
}
if (!mHookedGetGridHeight) {
try {
XposedHelpers.setObjectField(mQsPanel, "mGridHeight", h);
} catch (Throwable t) {
try {
XposedHelpers.findAndHookMethod(mQsPanel.getClass(), "getGridHeight", getGridHeightHook);
} catch (Throwable ignore) {
XposedHook.logW(TAG, "QSPanel#getGridHeight() doesn't exist!");
}
mHookedGetGridHeight = true;
}
}
// Used to clip header too
mGridHeight = h;
// TODO in N getGridHeight() returns getMeasuredHeight(), try that
mDetail.measure(exactly(width), View.MeasureSpec.UNSPECIFIED);
if (mDetail.getMeasuredHeight() < h) {
mDetail.measure(exactly(width), exactly(h));
}
if (isShowingDetail() && !isClosingDetail() && isExpanded()) {
h = mDetail.getMeasuredHeight();
}
XposedHelpers.callMethod(mQsPanel, "setMeasuredDimension", width, h);
}
protected void onLayout() {
final int w = mQsPanel.getWidth();
mBrightnessView.layout(0, 0, w, mBrightnessView.getMeasuredHeight());
int viewPagerBottom = mBrightnessView.getMeasuredHeight() + mTileLayout.getMeasuredHeight();
// view pager laid out from top of brightness view to bottom to page through settings
mTileLayout.layout(0, mBrightnessView.getMeasuredHeight(), w, viewPagerBottom);
mDetail.layout(0, 0, w, mDetail.getMeasuredHeight());
if ((boolean) XposedHelpers.callMethod(mFooter, "hasFooter")) {
View footer = (View) XposedHelpers.callMethod(mFooter, "getView");
footer.layout(0, mQsPanel.getMeasuredHeight() - footer.getMeasuredHeight(),
footer.getMeasuredWidth(), mQsPanel.getMeasuredHeight());
}
if (!isShowingDetail() && !isClosingDetail()) {
mBrightnessView.bringToFront();
}
}
private boolean isShowingDetail() {
return XposedHelpers.getObjectField(mQsPanel, "mDetailRecord") != null;
}
private boolean isClosingDetail() {
return XposedHelpers.getBooleanField(mQsPanel, "mClosingDetail");
}
private boolean isExpanded() {
return XposedHelpers.getBooleanField(mQsPanel, "mExpanded");
}
private static int exactly(int size) {
return View.MeasureSpec.makeMeasureSpec(size, View.MeasureSpec.EXACTLY);
}
protected String getHookClass() {
return CLASS_QS_PANEL;
}
public PagedTileLayout getTileLayout() {
return mTileLayout;
}
public int getGridHeight() {
return mGridHeight;
}
public View getBrightnessView() {
return mBrightnessView;
}
QSDetail setupQsDetail(ViewGroup panel, ViewGroup header) {
mQSDetail = new QSDetail(panel.getContext(), panel, header);
return mQSDetail;
}
public interface QSTileLayout {
void addTile(Object tile);
void removeTile(Object tile);
int getOffsetTop(Object tile);
boolean updateResources();
}
}