package tk.wasdennnoch.androidn_ify.systemui.qs; import android.animation.Animator; import android.animation.AnimatorListenerAdapter; import android.graphics.Rect; import android.view.ViewGroup; import android.view.ViewTreeObserver; import android.widget.FrameLayout; import android.widget.TextView; import java.lang.reflect.Method; import de.robv.android.xposed.XC_MethodHook; import de.robv.android.xposed.XposedHelpers; import tk.wasdennnoch.androidn_ify.R; import tk.wasdennnoch.androidn_ify.extracted.systemui.Interpolators; import tk.wasdennnoch.androidn_ify.extracted.systemui.qs.QSDetail; import tk.wasdennnoch.androidn_ify.systemui.notifications.StatusBarHeaderHooks; import tk.wasdennnoch.androidn_ify.utils.ConfigUtils; import tk.wasdennnoch.androidn_ify.utils.ResourceUtils; @SuppressWarnings("ResourceType") public class QSContainerHelper { private static final String TAG = "QSContainerHelper"; private final ViewGroup mNotificationPanelView; private final ViewGroup mHeader; private final ViewGroup mQSContainer; private final ViewGroup mQSPanel; private final QSDetail mQSDetail; //private final View mQSDetail; private float mQsExpansion; private int mHeaderHeight; private int mQsTopMargin; private static final int CAP_HEIGHT = 1456; private static final int FONT_HEIGHT = 2163; private Object mNotificationStackScroller; private Object mKeyguardStatusView; private TextView mClockView; private Rect mQsBounds = new Rect(); private boolean mKeyguardShowing = false; private boolean mHeaderAnimating; public QSContainerHelper(ViewGroup notificationPanelView, ViewGroup qsContainer, ViewGroup header, ViewGroup qsPanel) { mNotificationPanelView = notificationPanelView; mQSContainer = qsContainer; mHeader = header; mQSPanel = qsPanel; mQSPanel.setClipToPadding(false); mQSContainer.setPadding(0, 0, 0, 0); mQSDetail = StatusBarHeaderHooks.qsHooks.setupQsDetail(mQSPanel, mHeader); //((ViewGroup) mHeader.getParent()).removeView(mHeader); //mQSContainer.addView(mHeader); mQSContainer.addView(mQSDetail); //mQSDetail = (View) XposedHelpers.getObjectField(mQSPanel, "mDetail"); ResourceUtils res = ResourceUtils.getInstance(qsContainer.getContext()); mHeaderHeight = res.getDimensionPixelSize(R.dimen.status_bar_header_height); mQsTopMargin = res.getDimensionPixelSize(R.dimen.qs_margin_top); mQSPanel.setPadding(0, 0, 0, res.getDimensionPixelSize(R.dimen.qs_padding_bottom)); FrameLayout.LayoutParams qsPanelLp = (FrameLayout.LayoutParams) mQSPanel.getLayoutParams(); qsPanelLp.setMargins(0, res.getDimensionPixelSize(R.dimen.qs_margin_top), 0, 0); qsPanel.setLayoutParams(qsPanelLp); setUpOnLayout(); } public void setQsExpansion(float expansion, float headerTranslation) { expansion = Math.max(0, expansion); boolean keyguardShowing = XposedHelpers.getBooleanField(mNotificationPanelView, "mKeyguardShowing"); if (mKeyguardShowing != keyguardShowing) { mKeyguardShowing = keyguardShowing; if (mKeyguardShowing) { expansion = 0; XposedHelpers.setFloatField(mNotificationPanelView, "mQsExpansionHeight", expansion); } } mQsExpansion = expansion; final float translationScaleY = expansion - 1; if (!mHeaderAnimating) { float translation = keyguardShowing ? (translationScaleY * mHeader.getHeight()) : headerTranslation; mQSContainer.setTranslationY(translation); mHeader.setTranslationY(translation); } XposedHelpers.callMethod(mHeader, "setExpansion", keyguardShowing ? 1 : expansion); float qsPanelTranslationY = translationScaleY * mQSPanel.getHeight(); mQSPanel.setTranslationY(qsPanelTranslationY); mQSDetail.setFullyExpanded(expansion == 1); // TODO implement this //mQSDetail.setTranslationY(keyguardShowing ? 0 : -qsPanelTranslationY); updateBottom(); // Set bounds on the QS panel so it doesn't run over the header. mQsBounds.top = (int) (mQSPanel.getHeight() * (1 - expansion)); mQsBounds.right = mQSPanel.getWidth(); mQsBounds.bottom = mQSPanel.getHeight(); mQSPanel.setClipBounds(mQsBounds); } public void updateBottom() { int height = calculateContainerHeight(); mQSContainer.setBottom(mQSContainer.getTop() + height); //mQSDetail.setBottom(mQSContainer.getTop() + height); } private int calculateContainerHeight() { int mHeightOverride = XposedHelpers.getIntField(mQSContainer, "mHeightOverride"); int heightOverride = mHeightOverride != -1 ? mHeightOverride : mQSContainer.getMeasuredHeight() - mHeaderHeight; return (int) (mQsExpansion * heightOverride) + mHeaderHeight; } private void setUpOnLayout() { mNotificationStackScroller = XposedHelpers.getObjectField(mNotificationPanelView, "mNotificationStackScroller"); mKeyguardStatusView = XposedHelpers.getObjectField(mNotificationPanelView, "mKeyguardStatusView"); mClockView = (TextView) XposedHelpers.getObjectField(mNotificationPanelView, "mClockView"); } public void notificationPanelViewOnLayout(XC_MethodHook.MethodHookParam param, Class<?> classPanelView) { // TODO Too much work for changing just 2 integers? Maybe we could find a better way int left = (int) param.args[1], top = (int) param.args[2], right = (int) param.args[3], bottom = (int) param.args[4]; FrameLayout notificationPanelView = (FrameLayout) param.thisObject; // FrameLayout.onLayout XposedHelpers.callMethod(notificationPanelView, "layoutChildren", left, top, right, bottom, false); // PanelView.onLayout XposedHelpers.callMethod(notificationPanelView, "requestPanelHeightUpdate"); XposedHelpers.setBooleanField(notificationPanelView, "mHasLayoutedSinceDown", true); if (XposedHelpers.getBooleanField(notificationPanelView, "mUpdateFlingOnLayout")) { Method abortAnimations = XposedHelpers.findMethodBestMatch(classPanelView, "abortAnimations"); try { abortAnimations.invoke(notificationPanelView); } catch (Throwable ignore) { } XposedHelpers.callMethod(notificationPanelView, "fling", XposedHelpers.getFloatField(notificationPanelView, "mUpdateFlingVelocity"), true); XposedHelpers.setBooleanField(notificationPanelView, "mUpdateFlingOnLayout", false); } // NotificationPanelView.onLayout XposedHelpers.callMethod(mKeyguardStatusView, "setPivotX", notificationPanelView.getWidth() / 2); XposedHelpers.callMethod(mKeyguardStatusView, "setPivotY", (FONT_HEIGHT - CAP_HEIGHT) / 2048f * mClockView.getTextSize()); // Calculate quick setting heights. int oldMaxHeight = XposedHelpers.getIntField(notificationPanelView, "mQsMaxExpansionHeight"); int mQsMinExpansionHeight = XposedHelpers.getBooleanField(notificationPanelView, "mKeyguardShowing") ? 0 : mHeaderHeight; int mQsMaxExpansionHeight = (int) XposedHelpers.callMethod(mQSContainer, "getDesiredHeight"); XposedHelpers.setIntField(notificationPanelView, "mQsMinExpansionHeight", mQsMinExpansionHeight); XposedHelpers.setIntField(notificationPanelView, "mQsMaxExpansionHeight", mQsMaxExpansionHeight); XposedHelpers.callMethod(notificationPanelView, "positionClockAndNotifications"); boolean mQsExpanded = XposedHelpers.getBooleanField(notificationPanelView, "mQsExpanded"); if (mQsExpanded && XposedHelpers.getBooleanField(notificationPanelView, "mQsFullyExpanded")) { XposedHelpers.setIntField(notificationPanelView, "mQsExpansionHeight", mQsMaxExpansionHeight); XposedHelpers.callMethod(notificationPanelView, "requestScrollerTopPaddingUpdate", false); if (ConfigUtils.M) { XposedHelpers.callMethod(notificationPanelView, "requestPanelHeightUpdate"); // Size has changed, start an animation. if (mQsMaxExpansionHeight != oldMaxHeight) { XposedHelpers.callMethod(notificationPanelView, "startQsSizeChangeAnimation", oldMaxHeight, mQsMaxExpansionHeight); } } } else if (!mQsExpanded) { setQsExpansion((float) XposedHelpers.callMethod(param.thisObject, "getQsExpansionFraction"), (float) XposedHelpers.callMethod(param.thisObject, "getHeaderTranslation")); if (!ConfigUtils.M) { XposedHelpers.callMethod(mNotificationStackScroller, "setStackHeight", (float) XposedHelpers.callMethod(notificationPanelView, "getExpandedHeight")); XposedHelpers.callMethod(notificationPanelView, "updateHeader"); } } if (ConfigUtils.M) { XposedHelpers.callMethod(notificationPanelView, "updateStackHeight", (float) XposedHelpers.callMethod(notificationPanelView, "getExpandedHeight")); XposedHelpers.callMethod(notificationPanelView, "updateHeader"); } XposedHelpers.callMethod(mNotificationStackScroller, "updateIsSmallScreen", mHeaderHeight); if (ConfigUtils.M) { // If we are running a size change animation, the animation takes care of the height of // the container. However, if we are not animating, we always need to make the QS container // the desired height so when closing the QS detail, it stays smaller after the size change // animation is finished but the detail view is still being animated away (this animation // takes longer than the size change animation). if (XposedHelpers.getObjectField(notificationPanelView, "mQsSizeChangeAnimator") == null) { if (mQsMaxExpansionHeight != -1) mQsMaxExpansionHeight -= mHeaderHeight; XposedHelpers.callMethod(mQSContainer, "setHeightOverride", mQsMaxExpansionHeight); } XposedHelpers.callMethod(notificationPanelView, "updateMaxHeadsUpTranslation"); } } public int getDesiredHeight() { if (mQSDetail.isClosingDetail()) { return (int) XposedHelpers.callMethod(mQSPanel, "getGridHeight") + mQsTopMargin + mQSContainer.getPaddingBottom(); } else { return mQSContainer.getMeasuredHeight(); } } public void animateHeaderSlidingIn() { if (!XposedHelpers.getBooleanField(mNotificationPanelView, "mQsExpanded")) { mHeaderAnimating = true; mQSContainer.getViewTreeObserver().addOnPreDrawListener(mStartHeaderSlidingIn); } } public void animateHeaderSlidingOut() { mHeaderAnimating = true; mQSContainer.animate().y(-mHeader.getHeight()) .setStartDelay(0) .setDuration(360) .setInterpolator(Interpolators.FAST_OUT_SLOW_IN) .setListener(new AnimatorListenerAdapter() { @Override public void onAnimationEnd(Animator animation) { mQSContainer.animate().setListener(null); mHeaderAnimating = false; XposedHelpers.callMethod(mNotificationPanelView, "updateQsState"); } }) .start(); } private final ViewTreeObserver.OnPreDrawListener mStartHeaderSlidingIn = new ViewTreeObserver.OnPreDrawListener() { @Override public boolean onPreDraw() { mQSContainer.getViewTreeObserver().removeOnPreDrawListener(this); mQSContainer.animate() .translationY(0f) .setDuration(448) .setInterpolator(Interpolators.FAST_OUT_SLOW_IN) .setListener(mAnimateHeaderSlidingInListener) .start(); mQSContainer.setY(-mHeader.getHeight()); return true; } }; private final Animator.AnimatorListener mAnimateHeaderSlidingInListener = new AnimatorListenerAdapter() { @Override public void onAnimationEnd(Animator animation) { mHeaderAnimating = false; XposedHelpers.callMethod(mNotificationPanelView, "updateQsState"); } }; public int getBottom() { return mQSContainer.getBottom(); } public QSDetail getQSDetail() { return mQSDetail; } }