package tk.wasdennnoch.androidn_ify.systemui.notifications; import android.annotation.SuppressLint; import android.content.Context; import android.content.res.Configuration; import android.content.res.XModuleResources; import android.content.res.XResources; import android.graphics.Outline; import android.graphics.Rect; import android.graphics.Typeface; import android.graphics.drawable.Animatable; import android.graphics.drawable.Drawable; import android.os.Build; import android.util.AttributeSet; import android.util.TypedValue; import android.view.Gravity; import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; import android.view.ViewOutlineProvider; import android.widget.Button; import android.widget.FrameLayout; import android.widget.ImageButton; import android.widget.ImageView; import android.widget.LinearLayout; import android.widget.RelativeLayout; import android.widget.Switch; import android.widget.TextView; import java.util.ArrayList; import java.util.Collection; import java.util.Objects; 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 de.robv.android.xposed.callbacks.XC_InitPackageResources; import de.robv.android.xposed.callbacks.XC_LayoutInflated; import tk.wasdennnoch.androidn_ify.R; import tk.wasdennnoch.androidn_ify.XposedHook; import tk.wasdennnoch.androidn_ify.extracted.systemui.AlphaOptimizedButton; import tk.wasdennnoch.androidn_ify.extracted.systemui.AlphaOptimizedImageView; import tk.wasdennnoch.androidn_ify.extracted.systemui.ExpandableIndicator; import tk.wasdennnoch.androidn_ify.extracted.systemui.NonInterceptingScrollView; import tk.wasdennnoch.androidn_ify.extracted.systemui.qs.QSAnimator; import tk.wasdennnoch.androidn_ify.extracted.systemui.qs.QuickQSPanel; import tk.wasdennnoch.androidn_ify.extracted.systemui.qs.TouchAnimator; import tk.wasdennnoch.androidn_ify.misc.SafeOnClickListener; import tk.wasdennnoch.androidn_ify.misc.SafeRunnable; import tk.wasdennnoch.androidn_ify.systemui.SystemUIHooks; import tk.wasdennnoch.androidn_ify.systemui.notifications.stack.StackScrollAlgorithmHooks; import tk.wasdennnoch.androidn_ify.systemui.qs.DetailViewManager; import tk.wasdennnoch.androidn_ify.systemui.qs.KeyguardMonitor; import tk.wasdennnoch.androidn_ify.systemui.qs.QSTileHostHooks; import tk.wasdennnoch.androidn_ify.systemui.qs.QuickSettingsHooks; import tk.wasdennnoch.androidn_ify.systemui.qs.tiles.hooks.BluetoothTileHook; import tk.wasdennnoch.androidn_ify.systemui.qs.tiles.hooks.CellularTileHook; import tk.wasdennnoch.androidn_ify.systemui.qs.tiles.hooks.WifiTileHook; import tk.wasdennnoch.androidn_ify.utils.ConfigUtils; import tk.wasdennnoch.androidn_ify.utils.ResourceUtils; import tk.wasdennnoch.androidn_ify.utils.RomUtils; import tk.wasdennnoch.androidn_ify.utils.ViewUtils; @SuppressLint("StaticFieldLeak") public class StatusBarHeaderHooks { private static final String TAG = "StatusBarHeaderHooks"; private static final int WRAP_CONTENT = ViewGroup.LayoutParams.WRAP_CONTENT; private static final String PACKAGE_SYSTEMUI = XposedHook.PACKAGE_SYSTEMUI; private static final String CLASS_STATUS_BAR_HEADER_VIEW = "com.android.systemui.statusbar.phone.StatusBarHeaderView"; private static final String CLASS_LAYOUT_VALUES = CLASS_STATUS_BAR_HEADER_VIEW + "$LayoutValues"; private static final String CLASS_QS_PANEL = "com.android.systemui.qs.QSPanel"; private static final String CLASS_QS_DRAG_PANEL = "com.android.systemui.qs.QSDragPanel"; private static final String CLASS_QS_TILE = "com.android.systemui.qs.QSTile"; private static final String CLASS_QS_STATE = CLASS_QS_TILE + "$State"; private static final String CLASS_QS_TILE_VIEW = "com.android.systemui.qs.QSTileView"; private static final String CLASS_DETAIL_ADAPTER = CLASS_QS_TILE + "$DetailAdapter"; private static boolean mCollapseAfterHideDatails = false; private static boolean mHideTunerIcon = false; private static boolean mHideEditTiles = false; private static boolean mHideCarrierLabel = false; private static boolean mShowFullAlarm; private static TouchAnimator mFirstHalfAnimator; private static TouchAnimator mSecondHalfAnimator; private static TouchAnimator mSettingsAlpha; public static ViewGroup mStatusBarHeaderView; private static View mSystemIconsSuperContainer; private static View mDateGroup; private static AlphaOptimizedImageView mEdit; private static FrameLayout mMultiUserSwitch; private static View mClock; private static TextView mDateCollapsed; private static TextView mDateExpanded; private static ImageButton mSettingsButton; private static View mSettingsContainer; private static View mQsDetailHeader; private static TextView mQsDetailHeaderTitle; private static Switch mQsDetailHeaderSwitch; private static TextView mAlarmStatus; private static TextView mEditTileDoneText; private static View mTunerIcon; private static LinearLayout mWeatherContainer; private static View mTaskManagerButton; private static View mCustomQSEditButton; private static View mCustomQSEditButton2; private static View mCarrierText = null; private static ExpandableIndicator mExpandIndicator; private static LinearLayout mDateTimeAlarmGroup; private static LinearLayout mDateTimeGroup; private static LinearLayout mLeftContainer; private static LinearLayout mRightContainer; private static Button mAlarmStatusCollapsed; private static QuickQSPanel mHeaderQsPanel; public static ViewGroup mQsPanel; public static ViewGroup mQsContainer; public static Context mContext; private static ResourceUtils mResUtils; private static View mCurrentDetailView; private static ImageView mQsRightButton; private static boolean mHasEditPanel = false; public static boolean mShowingDetail; public static boolean mUseDragPanel = false; public static boolean mExpanded; private static float mExpansion = 0; private static boolean mRecreatingStatusBar = false; private static int mQsTopMargin; private static final ArrayList<String> mPreviousTiles = new ArrayList<>(); public static ArrayList<Object> mRecords; private static final Rect mClipBounds = new Rect(); public static QSAnimator mQsAnimator; public static QuickSettingsHooks qsHooks; private static final XC_MethodHook onFinishInflateHook = new XC_MethodHook() { @Override protected void afterHookedMethod(MethodHookParam param) throws Throwable { XposedHook.logD(TAG, "onFinishInflateHook called"); mStatusBarHeaderView = (ViewGroup) param.thisObject; mContext = mStatusBarHeaderView.getContext(); mResUtils = ResourceUtils.getInstance(mContext); ResourceUtils res = mResUtils; ConfigUtils config = ConfigUtils.getInstance(); mShowFullAlarm = res.getResources().getBoolean(R.bool.quick_settings_show_full_alarm) || config.qs.force_old_date_position; try { if (!config.qs.keep_header_background) { //noinspection deprecation mStatusBarHeaderView.setBackgroundColor(config.qs.fix_header_space ? 0 : mContext.getResources().getColor(mContext.getResources().getIdentifier("system_primary_color", "color", PACKAGE_SYSTEMUI))); } } catch (Throwable t) { XposedHook.logE(TAG, "Couldn't change header background color", t); } TextView mTime; TextView mAmPm; TextView mEmergencyCallsOnly; TextView mWeatherLine1 = null; TextView mWeatherLine2 = null; try { mSystemIconsSuperContainer = (View) XposedHelpers.getObjectField(param.thisObject, "mSystemIconsSuperContainer"); mDateGroup = (View) XposedHelpers.getObjectField(param.thisObject, "mDateGroup"); mClock = (View) XposedHelpers.getObjectField(param.thisObject, "mClock"); mTime = (TextView) XposedHelpers.getObjectField(param.thisObject, "mTime"); mAmPm = (TextView) XposedHelpers.getObjectField(param.thisObject, "mAmPm"); mMultiUserSwitch = (FrameLayout) XposedHelpers.getObjectField(param.thisObject, "mMultiUserSwitch"); mDateCollapsed = (TextView) XposedHelpers.getObjectField(param.thisObject, "mDateCollapsed"); mDateExpanded = (TextView) XposedHelpers.getObjectField(param.thisObject, "mDateExpanded"); mSettingsButton = (ImageButton) XposedHelpers.getObjectField(param.thisObject, "mSettingsButton"); mQsDetailHeader = (View) XposedHelpers.getObjectField(param.thisObject, "mQsDetailHeader"); mQsDetailHeaderTitle = (TextView) XposedHelpers.getObjectField(param.thisObject, "mQsDetailHeaderTitle"); mQsDetailHeaderSwitch = (Switch) XposedHelpers.getObjectField(param.thisObject, "mQsDetailHeaderSwitch"); mEmergencyCallsOnly = (TextView) XposedHelpers.getObjectField(param.thisObject, "mEmergencyCallsOnly"); mAlarmStatus = (TextView) XposedHelpers.getObjectField(param.thisObject, "mAlarmStatus"); if (mHasEditPanel) { mEditTileDoneText = (TextView) XposedHelpers.getObjectField(param.thisObject, "mEditTileDoneText"); } } catch (Throwable t) { XposedHook.logE(TAG, "Couldn't find required views, aborting", t); return; } // Separate try-catch for settings button as some ROMs removed the container around it try { mSettingsContainer = (View) XposedHelpers.getObjectField(param.thisObject, "mSettingsContainer"); } catch (Throwable t) { mSettingsContainer = mSettingsButton; } mTunerIcon = mSettingsContainer.findViewById(mContext.getResources().getIdentifier("tuner_icon", "id", PACKAGE_SYSTEMUI)); mHideTunerIcon = config.qs.hide_tuner_icon; mHideEditTiles = config.qs.hide_edit_tiles; mHideCarrierLabel = config.qs.hide_carrier_label; // workaround for a bug where the clock would get a wrong position when opening the detail view View dummyClock = new View(mContext); dummyClock.setVisibility(View.GONE); XposedHelpers.setObjectField(param.thisObject, "mClock", dummyClock); try { mWeatherContainer = (LinearLayout) XposedHelpers.getObjectField(param.thisObject, "mWeatherContainer"); mWeatherLine1 = (TextView) XposedHelpers.getObjectField(param.thisObject, "mWeatherLine1"); mWeatherLine2 = (TextView) XposedHelpers.getObjectField(param.thisObject, "mWeatherLine2"); } catch (Throwable ignore) { } try { mCarrierText = (View) XposedHelpers.getObjectField(param.thisObject, "mCarrierText"); } catch (Throwable ignore) { } try { mTaskManagerButton = (View) XposedHelpers.getObjectField(param.thisObject, "mTaskManagerButton"); } catch (Throwable ignore) { } try { // Sony mCustomQSEditButton = (View) XposedHelpers.getObjectField(param.thisObject, "mSomcQuickSettings"); } catch (Throwable i) { try { // PA mCustomQSEditButton = (View) XposedHelpers.getObjectField(param.thisObject, "mQsAddButton"); } catch (Throwable g) { try { // OOS2 & 3 mCustomQSEditButton = (View) XposedHelpers.getObjectField(param.thisObject, "mEditModeButton"); mCustomQSEditButton2 = (View) XposedHelpers.getObjectField(param.thisObject, "mResetButton"); XposedHelpers.setObjectField(param.thisObject, "mEditModeButton", new ImageView(mContext)); } catch (Throwable ignore) { } } } try { // PA seems to screw around a bit (the image has the bg color and overlaps the expand indicator ripple) ((View) XposedHelpers.getObjectField(param.thisObject, "mBackgroundImage")).setVisibility(View.GONE); } catch (Throwable ignore) { } try { boolean mShowTaskManager = true; try { mShowTaskManager = XposedHelpers.getBooleanField(param.thisObject, "mShowTaskManager"); } catch (Throwable ignore) { } int rippleRes = mContext.getResources().getIdentifier("ripple_drawable", "drawable", XposedHook.PACKAGE_SYSTEMUI); int rightIconHeight = res.getDimensionPixelSize(R.dimen.right_icon_size); int rightIconWidth = mTaskManagerButton != null && mShowTaskManager ? res.getDimensionPixelSize(R.dimen.right_icon_width_small) : rightIconHeight; int expandIndicatorPadding = res.getDimensionPixelSize(R.dimen.expand_indicator_padding); int headerItemsMarginTop = res.getDimensionPixelSize(R.dimen.header_items_margin_top); int alarmStatusTextColor = mAlarmStatus.getCurrentTextColor(); int dateTimeCollapsedSize = res.getDimensionPixelSize(R.dimen.date_time_collapsed_size); int dateTimeTextColor = mTime.getCurrentTextColor(); int dateCollapsedDrawablePadding = res.getDimensionPixelSize(R.dimen.date_collapsed_drawable_padding); int dateTimeMarginLeft = res.getDimensionPixelSize(R.dimen.date_time_alarm_group_margin_left); Drawable alarmSmall = mContext.getDrawable(mContext.getResources().getIdentifier("ic_access_alarms_small", "drawable", XposedHook.PACKAGE_SYSTEMUI)); ((ViewGroup) mClock.getParent()).removeView(mClock); ((ViewGroup) mMultiUserSwitch.getParent()).removeView(mMultiUserSwitch); ((ViewGroup) mDateCollapsed.getParent()).removeView(mDateCollapsed); ((ViewGroup) mSettingsContainer.getParent()).removeView(mSettingsContainer); ((ViewGroup) mAlarmStatus.getParent()).removeView(mAlarmStatus); ((ViewGroup) mEmergencyCallsOnly.getParent()).removeView(mEmergencyCallsOnly); createEditButton(rightIconHeight, rightIconWidth); int settingsIconSize = res.getDimensionPixelSize(R.dimen.settings_icon_size); Drawable settingsIcon = mSettingsButton.getDrawable(); settingsIcon.setBounds(0, 0, settingsIconSize, settingsIconSize); RelativeLayout.LayoutParams rightContainerLp = new RelativeLayout.LayoutParams(WRAP_CONTENT, res.getDimensionPixelSize(R.dimen.right_layout_height)); rightContainerLp.addRule(RelativeLayout.ALIGN_PARENT_END); rightContainerLp.rightMargin = res.getDimensionPixelSize(R.dimen.right_layout_margin_right); rightContainerLp.topMargin = res.getDimensionPixelSize(R.dimen.right_layout_margin_top); mRightContainer = new LinearLayout(mContext); mRightContainer.setLayoutParams(rightContainerLp); mRightContainer.setGravity(Gravity.CENTER); mRightContainer.setOrientation(LinearLayout.HORIZONTAL); mRightContainer.setClipChildren(false); LinearLayout.LayoutParams multiUserSwitchLp = new LinearLayout.LayoutParams(rightIconWidth, rightIconHeight); mMultiUserSwitch.setLayoutParams(multiUserSwitchLp); LinearLayout.LayoutParams settingsContainerLp = new LinearLayout.LayoutParams(rightIconWidth, rightIconHeight); mSettingsContainer.setLayoutParams(settingsContainerLp); LinearLayout.LayoutParams expandIndicatorLp = new LinearLayout.LayoutParams(rightIconHeight, rightIconHeight); // Requires full width mExpandIndicator = new ExpandableIndicator(mContext); mExpandIndicator.setLayoutParams(expandIndicatorLp); mExpandIndicator.setPadding(expandIndicatorPadding, expandIndicatorPadding, expandIndicatorPadding, expandIndicatorPadding); mExpandIndicator.setClickable(true); mExpandIndicator.setFocusable(true); mExpandIndicator.setFocusableInTouchMode(false); mExpandIndicator.setCropToPadding(false); mExpandIndicator.setBackgroundResource(rippleRes); mExpandIndicator.setId(R.id.statusbar_header_expand_indicator); RelativeLayout.LayoutParams leftContainerLp = new RelativeLayout.LayoutParams(WRAP_CONTENT, WRAP_CONTENT); leftContainerLp.addRule(RelativeLayout.ALIGN_PARENT_START); leftContainerLp.leftMargin = dateTimeMarginLeft; leftContainerLp.topMargin = headerItemsMarginTop; mLeftContainer = new LinearLayout(mContext); mLeftContainer.setLayoutParams(leftContainerLp); mLeftContainer.setOrientation(LinearLayout.VERTICAL); RelativeLayout.LayoutParams emergencyCallsOnlyLp = new RelativeLayout.LayoutParams(WRAP_CONTENT, WRAP_CONTENT); emergencyCallsOnlyLp.addRule(RelativeLayout.ALIGN_PARENT_TOP); mEmergencyCallsOnly.setLayoutParams(emergencyCallsOnlyLp); mEmergencyCallsOnly.setTextSize(TypedValue.COMPLEX_UNIT_PX, res.getDimensionPixelSize(R.dimen.emergency_calls_only_text_size)); mEmergencyCallsOnly.setTextColor(alarmStatusTextColor); mEmergencyCallsOnly.setPadding(0, 0, 0, 0); mEmergencyCallsOnly.setVisibility(View.GONE); RelativeLayout.LayoutParams dateTimeAlarmGroupLp = new RelativeLayout.LayoutParams(WRAP_CONTENT, WRAP_CONTENT); dateTimeAlarmGroupLp.addRule(RelativeLayout.ALIGN_PARENT_TOP); dateTimeAlarmGroupLp.topMargin = res.getDimensionPixelSize(R.dimen.date_time_alarm_group_margin_top); dateTimeAlarmGroupLp.leftMargin = dateTimeMarginLeft; mDateTimeAlarmGroup = new LinearLayout(mContext); mDateTimeAlarmGroup.setLayoutParams(dateTimeAlarmGroupLp); mDateTimeAlarmGroup.setId(View.generateViewId()); mDateTimeAlarmGroup.setGravity(Gravity.START); mDateTimeAlarmGroup.setOrientation(LinearLayout.VERTICAL); mDateTimeAlarmGroup.setBaselineAligned(false); mDateTimeAlarmGroup.setClipChildren(false); mDateTimeAlarmGroup.setClipToPadding(false); LinearLayout.LayoutParams alarmStatusLp = new LinearLayout.LayoutParams(WRAP_CONTENT, res.getDimensionPixelSize(R.dimen.alarm_status_height)); mAlarmStatus.setLayoutParams(alarmStatusLp); mAlarmStatus.setGravity(Gravity.TOP); mAlarmStatus.setTextSize(TypedValue.COMPLEX_UNIT_PX, dateTimeCollapsedSize); mAlarmStatus.setPadding(0, res.getDimensionPixelSize(R.dimen.alarm_status_padding_top), 0, 0); mAlarmStatus.setCompoundDrawablePadding(res.getDimensionPixelSize(R.dimen.alarm_status_drawable_padding)); mAlarmStatus.setCompoundDrawablesWithIntrinsicBounds(alarmSmall, null, null, null); mAlarmStatus.setVisibility(View.GONE); mAlarmStatus.setBackgroundResource(rippleRes); LinearLayout.LayoutParams dateTimeGroupLp = new LinearLayout.LayoutParams(WRAP_CONTENT, res.getDimensionPixelSize(R.dimen.date_time_group_height)); mDateTimeGroup = new LinearLayout(mContext); mDateTimeGroup.setLayoutParams(dateTimeGroupLp); mDateTimeGroup.setId(View.generateViewId()); mDateTimeGroup.setOrientation(LinearLayout.HORIZONTAL); mDateTimeGroup.setPivotX(0.0F); mDateTimeGroup.setPivotY(0.0F); mDateTimeGroup.setBaselineAligned(false); mDateTimeGroup.setClipChildren(false); mDateTimeGroup.setClipToPadding(false); LinearLayout.LayoutParams clockLp = new LinearLayout.LayoutParams(WRAP_CONTENT, WRAP_CONTENT); mClock.setLayoutParams(clockLp); mClock.findViewById(mContext.getResources().getIdentifier("empty_time_view", "id", XposedHook.PACKAGE_SYSTEMUI)).setVisibility(View.GONE); mTime.setTextColor(dateTimeTextColor); mTime.setTextSize(TypedValue.COMPLEX_UNIT_PX, dateTimeCollapsedSize); mAmPm.setTextColor(dateTimeTextColor); mAmPm.setTextSize(TypedValue.COMPLEX_UNIT_PX, dateTimeCollapsedSize); mAmPm.setPadding(0, 0, dateCollapsedDrawablePadding, 0); LinearLayout.LayoutParams dateCollapsedLp = new LinearLayout.LayoutParams(WRAP_CONTENT, WRAP_CONTENT); mDateCollapsed.setLayoutParams(dateCollapsedLp); mDateCollapsed.setGravity(Gravity.TOP); mDateCollapsed.setTextColor(dateTimeTextColor); mDateCollapsed.setTextSize(TypedValue.COMPLEX_UNIT_PX, dateTimeCollapsedSize); mDateCollapsed.setTypeface(Typeface.create("sans-serif-medium",Typeface.NORMAL)); if (mShowFullAlarm) { mDateCollapsed.setCompoundDrawablesWithIntrinsicBounds(res.getDrawable(R.drawable.header_dot), null, null, null); mDateCollapsed.setCompoundDrawablePadding(dateCollapsedDrawablePadding); } LinearLayout.LayoutParams alarmStatusCollapsedLp = new LinearLayout.LayoutParams(WRAP_CONTENT, WRAP_CONTENT); mAlarmStatusCollapsed = new AlphaOptimizedButton(mContext); mAlarmStatusCollapsed.setLayoutParams(alarmStatusCollapsedLp); mAlarmStatusCollapsed.setId(View.generateViewId()); mAlarmStatusCollapsed.setGravity(Gravity.TOP); mAlarmStatusCollapsed.setTextColor(alarmStatusTextColor); mAlarmStatusCollapsed.setTextSize(TypedValue.COMPLEX_UNIT_PX, dateTimeCollapsedSize); mAlarmStatusCollapsed.setClickable(false); mAlarmStatusCollapsed.setFocusable(false); mAlarmStatusCollapsed.setVisibility(View.GONE); mAlarmStatusCollapsed.setCompoundDrawablesWithIntrinsicBounds(alarmSmall, null, null, null); mAlarmStatusCollapsed.setBackgroundResource(0); mAlarmStatusCollapsed.setPadding(res.getDimensionPixelSize(mShowFullAlarm ? R.dimen.alarm_status_collapsed_drawable_padding : R.dimen.alarm_status_collapsed_drawable_padding_small) , 0, 0, 0); RelativeLayout.LayoutParams headerQsPanelLp = new RelativeLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, WRAP_CONTENT); mHeaderQsPanel = new QuickQSPanel(mContext); mHeaderQsPanel.setLayoutParams(headerQsPanelLp); mHeaderQsPanel.setClipChildren(false); mHeaderQsPanel.setClipToPadding(false); if (mWeatherContainer != null && mWeatherLine1 != null && mWeatherLine2 != null) { RelativeLayout.LayoutParams weatherContainerLp = new RelativeLayout.LayoutParams(WRAP_CONTENT, WRAP_CONTENT); weatherContainerLp.addRule(RelativeLayout.ALIGN_PARENT_TOP); //weatherContainerLp.addRule(RelativeLayout.ALIGN_PARENT_BOTTOM); weatherContainerLp.addRule(RelativeLayout.ALIGN_PARENT_END); weatherContainerLp.bottomMargin = headerItemsMarginTop; //mWeatherContainer.setOrientation(LinearLayout.HORIZONTAL); // TODO Setting the orientation completely fu**s it up?! Positioned at the parent top mWeatherContainer.setLayoutParams(weatherContainerLp); //mWeatherLine1.setLayoutParams(new LinearLayout.LayoutParams(WRAP_CONTENT, WRAP_CONTENT)); //mWeatherLine2.setLayoutParams(new LinearLayout.LayoutParams(WRAP_CONTENT, WRAP_CONTENT)); //int padding = mContext.getResources().getDimensionPixelSize(mContext.getResources().getIdentifier("status_bar_weather_padding_end", "dimen", PACKAGE_SYSTEMUI)); //mWeatherLine1.setPadding(0, 0, 0, 0); //mWeatherLine2.setPadding(0, 0, padding, 0); //TextView dash = new TextView(mWeatherLine2.getContext()); //dash.setText(" - "); //mWeatherContainer.addView(dash, 1, new LinearLayout.LayoutParams(WRAP_CONTENT, WRAP_CONTENT)); } if (mCarrierText != null) { ((ViewGroup) mCarrierText.getParent()).removeView(mCarrierText); RelativeLayout.LayoutParams carrierTextLp = new RelativeLayout.LayoutParams(WRAP_CONTENT, WRAP_CONTENT); mCarrierText.setLayoutParams(carrierTextLp); mCarrierText.setPadding(0, 0, 0, 0); } if (mTaskManagerButton != null) { ((ViewGroup) mTaskManagerButton.getParent()).removeView(mTaskManagerButton); LinearLayout.LayoutParams taskManagerButtonLp = new LinearLayout.LayoutParams(rightIconWidth, rightIconHeight); mTaskManagerButton.setLayoutParams(taskManagerButtonLp); } try { // OOS (and maybe more in the future) XposedHelpers.findMethodBestMatch(mStatusBarHeaderView.getClass(), "startDateActivity"); if (mStatusBarHeaderView instanceof View.OnClickListener) { View.OnClickListener l = (View.OnClickListener) mStatusBarHeaderView; mDateCollapsed.setOnClickListener(l); mDateExpanded.setOnClickListener(l); } } catch (Throwable ignore) { } if (mCarrierText != null) mLeftContainer.addView(mCarrierText); mLeftContainer.addView(mEmergencyCallsOnly); if (mTaskManagerButton != null) mRightContainer.addView(mTaskManagerButton); mRightContainer.addView(mMultiUserSwitch); mRightContainer.addView(mEdit); mRightContainer.addView(mSettingsContainer); mRightContainer.addView(mExpandIndicator); mDateTimeGroup.addView(mClock); if (mShowFullAlarm) { mDateTimeGroup.addView(mDateCollapsed); } mDateTimeGroup.addView(mAlarmStatusCollapsed); mDateTimeAlarmGroup.addView(mDateTimeGroup); mDateTimeAlarmGroup.addView(mAlarmStatus); // The view HAS to be attached to a parent, otherwise it apparently gets GC -> NPE. We hide it later if necessary if (!mShowFullAlarm) mDateTimeAlarmGroup.addView(mDateCollapsed); mStatusBarHeaderView.addView(mLeftContainer); mStatusBarHeaderView.addView(mRightContainer); mStatusBarHeaderView.addView(mDateTimeAlarmGroup); mStatusBarHeaderView.addView(mHeaderQsPanel); mStatusBarHeaderView.setClipChildren(false); mStatusBarHeaderView.setClipToPadding(false); mStatusBarHeaderView.setOutlineProvider(new ViewOutlineProvider() { @Override public void getOutline(View view, Outline outline) { outline.setRect(mClipBounds); } }); } catch (Throwable t) { // :( XposedHook.logE(TAG, "Error modifying header layout", t); return; } DetailViewManager.init(mContext, mStatusBarHeaderView, mQsPanel, mHasEditPanel); postSetupAnimators(); updateResources(); } }; private static void createEditButton(int height, int width) { mEdit = new AlphaOptimizedImageView(mContext); LinearLayout.LayoutParams editLp = new LinearLayout.LayoutParams(width, height); mEdit.setLayoutParams(editLp); int padding = mResUtils.getDimensionPixelSize(R.dimen.qs_edit_padding); TypedValue background = new TypedValue(); mContext.getTheme().resolveAttribute(android.R.attr.selectableItemBackgroundBorderless, background, true); mEdit.setId(R.id.qs_edit); mEdit.setClickable(true); mEdit.setFocusable(true); mEdit.setImageDrawable(mResUtils.getDrawable(R.drawable.ic_mode_edit)); mEdit.setBackground(mContext.getDrawable(background.resourceId)); mEdit.setPadding(padding, padding, padding, padding); mEdit.setOnClickListener(onClickListener); } private static final XC_MethodHook setExpansionHook = new XC_MethodHook() { @Override protected void beforeHookedMethod(MethodHookParam param) throws Throwable { float f = (float) param.args[0]; mExpanded = f > 0; mExpansion = f; try { if (mSettingsAlpha != null) { if (mShowFullAlarm) { mFirstHalfAnimator.setPosition(f); } mSecondHalfAnimator.setPosition(f); mSettingsAlpha.setPosition(f); mQsAnimator.setPosition(f); } mExpandIndicator.setExpanded(f > 0.93F); } catch (Throwable ignore) { // Oh god, a massive spam wall coming right at you, quick, hide! } } @Override protected void afterHookedMethod(MethodHookParam param) throws Throwable { int mGridHeight = qsHooks.getGridHeight(); if (mGridHeight == 0) return; View view = (View) param.thisObject; float height = view.getHeight(); height += (int) (mGridHeight * mExpansion); mClipBounds.set(view.getPaddingLeft(), 0, view.getWidth() - view.getPaddingRight(), (int) height); view.setClipBounds(mClipBounds); view.invalidateOutline(); } }; private static final XC_MethodHook onConfigurationChangedHook = new XC_MethodHook() { @Override protected void afterHookedMethod(MethodHookParam param) throws Throwable { updateResources(); } }; private static final XC_MethodHook updateVisibilitiesHook = new XC_MethodHook() { @Override protected void afterHookedMethod(MethodHookParam param) throws Throwable { if (mSystemIconsSuperContainer != null) { boolean mExpanded = XposedHelpers.getBooleanField(param.thisObject, "mExpanded"); mSystemIconsSuperContainer.setVisibility(View.GONE); mDateExpanded.setVisibility(View.GONE); mDateGroup.setVisibility(View.GONE); updateAlarmVisibilities(); mMultiUserSwitch.setVisibility(mExpanded ? View.VISIBLE : View.INVISIBLE); mEdit.setVisibility(ConfigUtils.qs().enable_qs_editor ? mExpanded ? View.VISIBLE : View.INVISIBLE : View.GONE); if (!mShowFullAlarm) { mAlarmStatus.setVisibility(View.GONE); mDateCollapsed.setVisibility(mExpanded ? View.VISIBLE : View.INVISIBLE); } else { mAlarmStatus.setVisibility(mExpanded && XposedHelpers.getBooleanField(mStatusBarHeaderView, "mAlarmShowing") ? View.VISIBLE : View.INVISIBLE); mDateCollapsed.setVisibility(View.VISIBLE); } mSettingsContainer.setVisibility(View.VISIBLE); if (mHideTunerIcon && mTunerIcon != null) mTunerIcon.setVisibility(View.INVISIBLE); if (mHideEditTiles && mCustomQSEditButton != null) { mCustomQSEditButton.setVisibility(View.GONE); if (mCustomQSEditButton2 != null) mCustomQSEditButton2.setVisibility(View.GONE); } if (mHideCarrierLabel && mCarrierText != null) mCarrierText.setVisibility(View.GONE); if (mWeatherContainer != null) { try { mWeatherContainer.setVisibility(mExpanded && XposedHelpers.getBooleanField(mStatusBarHeaderView, "mShowWeather") ? View.VISIBLE : View.INVISIBLE); } catch (Throwable ignored) { } } } } }; private static final XC_MethodHook setEditingHook = new XC_MethodHook() { @Override protected void afterHookedMethod(MethodHookParam param) throws Throwable { boolean editing = (boolean) param.args[0]; boolean shouldShowViews = !editing && XposedHelpers.getBooleanField(param.thisObject, "mExpanded"); if (mDateTimeAlarmGroup != null) { mDateTimeAlarmGroup.setVisibility(editing ? View.INVISIBLE : View.VISIBLE); mMultiUserSwitch.setVisibility(shouldShowViews ? View.VISIBLE : View.INVISIBLE); mSettingsContainer.setVisibility(shouldShowViews ? View.VISIBLE : View.INVISIBLE); mExpandIndicator.setVisibility(editing ? View.INVISIBLE : View.VISIBLE); } } }; private static final XC_MethodHook setTilesHook = new XC_MethodHook() { boolean cancelled = false; @Override protected void beforeHookedMethod(MethodHookParam param) throws Throwable { if (mHeaderQsPanel != null) { // keep XposedHook.logD(TAG, "setTilesHook Called"); if (mRecreatingStatusBar) { XposedHook.logD(TAG, "setTilesHook: Skipping changed check due to StatusBar recreation"); return; // Otherwise all tiles are gone after recreation } if (mUseDragPanel && !RomUtils.isAicp()) { XposedHook.logD(TAG, "setTilesHook: Skipping check because mUseDragPanel && !RomUtils.isAicp()"); return; // CM has tile caching logics } // Only set up views if the tiles actually changed if (param.args == null || param.args.length == 0) { XposedHook.logD(TAG, "setTilesHook: Skipping check because param.args == null || param.args.length == 0"); return; // PA already checks itself } Collection tiles = (Collection) param.args[0]; ArrayList<String> newTiles = new ArrayList<>(); for (Object qstile : tiles) { newTiles.add(qstile.getClass().getSimpleName()); } cancelled = false; if (mPreviousTiles.equals(newTiles)) { cancelled = true; XposedHook.logD(TAG, "setTilesHook: Cancelling original method because mPreviousTiles.equals(newTiles)"); param.setResult(null); return; } mPreviousTiles.clear(); mPreviousTiles.addAll(newTiles); } } @Override protected void afterHookedMethod(MethodHookParam param) throws Throwable { if (mHeaderQsPanel != null) { // keep try { //noinspection unchecked mRecords = (ArrayList<Object>) XposedHelpers.getObjectField(param.thisObject, "mRecords"); // OOS: sometimes mRecords still seems to be in the StatusBarHeaderView (but empty) if (mRecords.size() == 0) throw new Throwable(); } catch (Throwable t) { try { // OOS //noinspection unchecked mRecords = (ArrayList<Object>) XposedHelpers.getObjectField(XposedHelpers.getObjectField(param.thisObject, "mGridView"), "mRecords"); } catch (Throwable t2) { XposedHook.logE(TAG, "No tile record field found (" + t.getClass().getSimpleName() + " and " + t2.getClass().getSimpleName() + ")", null); return; } } if (!cancelled) { mHeaderQsPanel.setTiles(mRecords); } else { XposedHook.logD(TAG, "setTilesHook: Not setting tiles to header because cancelled"); } } } }; private static final XC_MethodHook handleStateChangedHook = new XC_MethodHook() { @Override protected void afterHookedMethod(MethodHookParam param) throws Throwable { // This method gets called from two different processes, so we have // to check if we are in the right one by testing if the panel is null if (mHeaderQsPanel != null) { Object state = XposedHelpers.getObjectField(param.thisObject, "mState"); mHeaderQsPanel.handleStateChanged(param.thisObject, state); NotificationPanelHooks.handleStateChanged(param.thisObject, state); } } }; private static final XC_MethodHook setupViewsHook = new XC_MethodHook() { @Override protected void afterHookedMethod(MethodHookParam param) throws Throwable { View pageIndicator = (View) XposedHelpers.getObjectField(param.thisObject, "mPageIndicator"); pageIndicator.setAlpha(0); } }; private static void wrapQsDetail(LinearLayout layout) { Context context = layout.getContext(); FrameLayout content = (FrameLayout) layout.findViewById(android.R.id.content); ViewUtils.setHeight(content, ViewGroup.LayoutParams.MATCH_PARENT); int position = layout.indexOfChild(content); layout.removeView(content); LinearLayout.LayoutParams scrollViewLp = new LinearLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, 0); scrollViewLp.weight = 1; NonInterceptingScrollView scrollView = new NonInterceptingScrollView(context); scrollView.setLayoutParams(scrollViewLp); scrollView.addView(content); scrollView.setFillViewport(true); layout.addView(scrollView, position); } private static final XC_MethodHook handleShowDetailImplHook = new XC_MethodHook() { @Override protected void beforeHookedMethod(MethodHookParam param) throws Throwable { boolean show = (boolean) param.args[1]; Object tileRecord = param.args[0]; if (show ? NotificationPanelHooks.isCollapsed() : mCollapseAfterHideDatails) { param.args[2] = mQsAnimator.getTileViewX(tileRecord); param.args[3] = 0; if (!show) { NotificationPanelHooks.collapseIfNecessary(); } } else { if (tileRecord != null) { try { if (ConfigUtils.qs().fix_header_space && mQsTopMargin == 0) { mQsTopMargin = mResUtils.getDimensionPixelSize(R.dimen.qs_margin_top); } View tileView = (View) XposedHelpers.getObjectField(tileRecord, "tileView"); param.args[2] = tileView.getLeft() + tileView.getWidth() / 2; param.args[3] = tileView.getTop() + qsHooks.getTileLayout().getOffsetTop(tileRecord) + tileView.getHeight() / 2 + mQsPanel.getTop(); } catch (Throwable ignore) { // OOS3 } } } mCollapseAfterHideDatails = false; } @Override protected void afterHookedMethod(MethodHookParam param) throws Throwable { boolean show = (boolean) param.args[1]; XposedHook.logD(TAG, "handleShowDetailImpl: " + (show ? "showing" : "hiding") + " detail; expanding: " + NotificationPanelHooks.isCollapsed() + ";"); if (show && NotificationPanelHooks.isCollapsed()) { mCollapseAfterHideDatails = true; NotificationPanelHooks.expandIfNecessary(); } } }; private static void updateResources() { if (mDateTimeGroup == null) { return; } if (mShowFullAlarm) { mFirstHalfAnimator = new TouchAnimator.Builder() .addFloat(mAlarmStatusCollapsed, "alpha", 1.0F, 0.0F) .build(); } mSecondHalfAnimator = new TouchAnimator.Builder() .addFloat(mShowFullAlarm ? mAlarmStatus : mDateCollapsed, "alpha", 0.0F, 1.0F) .build(); TouchAnimator.Builder settingsAlphaBuilder = new TouchAnimator.Builder() .addFloat(mEdit, "alpha", 0.0F, 1.0F) .addFloat(mMultiUserSwitch, "alpha", 0.0F, 1.0F) .addFloat(mLeftContainer, "alpha", 0.0F, 1.0F) .setStartDelay(0.7F); if (mWeatherContainer != null) { settingsAlphaBuilder .addFloat(mWeatherContainer, "alpha", 0.0F, 1.0F); } if (mTaskManagerButton != null) { settingsAlphaBuilder .addFloat(mTaskManagerButton, "alpha", 0.0F, 1.0F); } mSettingsAlpha = settingsAlphaBuilder.build(); boolean rtl = (boolean) XposedHelpers.callMethod(mStatusBarHeaderView.getLayoutParams(), "isLayoutRtl"); if (rtl && mDateTimeGroup.getWidth() == 0) { if (mDateTimeGroup.getWidth() == 0) { mDateTimeGroup.addOnLayoutChangeListener(new android.view.View.OnLayoutChangeListener() { @Override public void onLayoutChange(View view, int j, int k, int l, int i1, int j1, int k1, int l1, int i2) { mDateTimeGroup.setPivotX(mStatusBarHeaderView.getWidth()); mDateTimeGroup.removeOnLayoutChangeListener(this); } }); } else { mDateTimeGroup.setPivotX(mDateTimeGroup.getWidth()); } } } private static void updateAlarmVisibilities() { boolean mAlarmShowing = XposedHelpers.getBooleanField(mStatusBarHeaderView, "mAlarmShowing"); if (mAlarmStatusCollapsed != null) mAlarmStatusCollapsed.setVisibility(mAlarmShowing ? View.VISIBLE : View.INVISIBLE); } private static void handleShowingDetail(final Object detail) { if (ConfigUtils.qs().fix_header_space) return; final boolean showingDetail = detail != null; mCurrentDetailView = getCurrentDetailView(); int rightButtonVisibility = View.GONE; DetailViewManager.DetailViewAdapter detailViewAdapter = DetailViewManager.getInstance().getDetailViewAdapter(mCurrentDetailView); if (detailViewAdapter != null && detailViewAdapter.hasRightButton()) { rightButtonVisibility = View.VISIBLE; mQsRightButton.setImageDrawable(mResUtils.getDrawable(detailViewAdapter.getRightButtonResId())); } mQsRightButton.setVisibility(rightButtonVisibility); // Fixes an issue with the indicator having two backgrounds when layer type is hardware mExpandIndicator.setLayerType(View.LAYER_TYPE_NONE, null); transition(mDateTimeAlarmGroup, !showingDetail); transition(mRightContainer, !showingDetail); transition(mExpandIndicator, !showingDetail); if (mExpansion < 1) transition(mHeaderQsPanel, !showingDetail); if (mWeatherContainer != null) { try { if (XposedHelpers.getBooleanField(mStatusBarHeaderView, "mShowWeather")) transition(mWeatherContainer, !showingDetail); } catch (Throwable ignored) { } } transition(mQsDetailHeader, showingDetail); mShowingDetail = showingDetail; XposedHelpers.setBooleanField(mStatusBarHeaderView, "mShowingDetail", showingDetail); if (showingDetail) { XposedHook.logD(TAG, "handleShowingDetail: showing detail; " + detail.getClass().getSimpleName()); try { mQsDetailHeaderTitle.setText((int) XposedHelpers.callMethod(detail, "getTitle")); } catch (Throwable t) { Context context = mQsDetailHeaderTitle.getContext(); Class<?> classQSTile = XposedHelpers.findClass(CLASS_QS_TILE, context.getClassLoader()); mQsDetailHeaderTitle.setText((String) XposedHelpers.callStaticMethod(classQSTile, "getDetailAdapterTitle", context, detail)); } final Boolean toggleState = (Boolean) XposedHelpers.callMethod(detail, "getToggleState"); if (toggleState == null) { mQsDetailHeaderSwitch.setVisibility(View.INVISIBLE); mQsDetailHeader.setClickable(false); } else { mQsDetailHeaderSwitch.setVisibility(View.VISIBLE); mQsDetailHeaderSwitch.setChecked(toggleState); mQsDetailHeader.setClickable(true); mQsDetailHeader.setOnClickListener(new SafeOnClickListener(TAG, "Error in mQsDetailHeader click listener") { @Override public void onClickSafe(View v) { boolean checked = !mQsDetailHeaderSwitch.isChecked(); mQsDetailHeaderSwitch.setChecked(checked); XposedHelpers.callMethod(detail, "setToggleState", checked); } }); } if (mHasEditPanel) { if ((int) XposedHelpers.callMethod(detail, "getTitle") == mQsDetailHeader.getResources().getIdentifier("quick_settings_edit_label", "string", PACKAGE_SYSTEMUI)) { mEditTileDoneText.setVisibility(View.VISIBLE); } else { mEditTileDoneText.setVisibility(View.GONE); } } } else { XposedHook.logD(TAG, "handleShowingDetail: hiding detail; collapsing: " + mCollapseAfterHideDatails); mQsDetailHeader.setClickable(false); } } private static View getCurrentDetailView() { Object detailRecord = XposedHelpers.getObjectField(mQsPanel, "mDetailRecord"); if (detailRecord != null) { Object detailView = XposedHelpers.getObjectField(detailRecord, "detailView"); if (detailView != null && detailView instanceof View) { return (View) detailView; } } return null; } public static void transition(final View v, final boolean in) { if (in) { v.bringToFront(); v.setVisibility(View.VISIBLE); } if (v.hasOverlappingRendering()) { v.animate().withLayer(); } v.animate() .alpha(in ? 1 : 0) .withEndAction(new SafeRunnable() { @Override public void runSafe() { if (!in) { v.setVisibility(View.INVISIBLE); } if (!ConfigUtils.M) XposedHelpers.setBooleanField(mStatusBarHeaderView, "mDetailTransitioning", false); } }) .start(); } private static final View.OnClickListener onClickListener = new SafeOnClickListener(TAG, "Error in onClickListener") { @Override public void onClickSafe(View v) { switch (v.getId()) { case R.id.qs_right: if (mCurrentDetailView != null && mCurrentDetailView instanceof DetailViewManager.DetailViewAdapter) { ((DetailViewManager.DetailViewAdapter) mCurrentDetailView).handleRightButtonClick(); } break; case R.id.qs_edit: onClickEdit(mRightContainer.getLeft() + mEdit.getLeft() + mEdit.getWidth() / 2, mEdit.getTop() + mEdit.getHeight() / 2); break; } } }; private static void onClickEdit(int vx, int vy) { final int x = StackScrollAlgorithmHooks.mStackScrollLayout.getLeft() + vx; showEditDismissingKeyguard(x, vy); } public static void showEditDismissingKeyguard(final int x, final int y) { SystemUIHooks.startRunnableDismissingKeyguard(new Runnable() { @Override public void run() { showEdit(x, y); } }); } private static void showEdit(final int x, final int y) { mQsPanel.post(new Runnable() { @Override public void run() { NotificationPanelHooks.showQsCustomizer(mRecords, x, y); } }); } private static KeyguardMonitor.Callback mKeyguardCallback = new KeyguardMonitor.Callback() { @Override public void onKeyguardChanged() { postSetupAnimatorsImpl(); QSTileHostHooks.mKeyguard.removeCallback(this); } }; public static void postSetupAnimators() { XposedHook.logD(TAG, "postSetupAnimators called"); KeyguardMonitor keyguardMonitor = QSTileHostHooks.mKeyguard; if (keyguardMonitor == null || !keyguardMonitor.isShowing()) postSetupAnimatorsImpl(); else keyguardMonitor.addCallback(mKeyguardCallback); } private static void postSetupAnimatorsImpl() { XposedHook.logD(TAG, "postSetupAnimatorsImpl called"); // Wait until the layout is set up // It already works after 2 frames on my device, but just to be sure use 3 mQsPanel.post(new Runnable() { @Override public void run() { mQsPanel.post(new Runnable() { @Override public void run() { mQsPanel.post(new SafeRunnable() { @Override public void runSafe() { if (mQsAnimator != null) { mQsAnimator.mUpdateAnimators.run(); } } }); } }); } }); } public static void hook(ClassLoader classLoader) { try { if (ConfigUtils.qs().header) { qsHooks = QuickSettingsHooks.create(classLoader); Class<?> classStatusBarHeaderView = XposedHelpers.findClass(CLASS_STATUS_BAR_HEADER_VIEW, classLoader); Class<?> classLayoutValues = XposedHelpers.findClass(CLASS_LAYOUT_VALUES, classLoader); Class<?> classQSPanel = XposedHelpers.findClass(CLASS_QS_PANEL, classLoader); Class<?> classQSTile = XposedHelpers.findClass(CLASS_QS_TILE, classLoader); Class<?> classQSTileView = XposedHelpers.findClass(CLASS_QS_TILE_VIEW, classLoader); Class<?> classPhoneStatusBar = XposedHelpers.findClass("com.android.systemui.statusbar.phone.PhoneStatusBar", classLoader); try { XposedHelpers.findAndHookMethod(classStatusBarHeaderView, "setEditing", boolean.class, setEditingHook); mHasEditPanel = true; } catch (NoSuchMethodError ignore) { mHasEditPanel = false; } XposedHelpers.findAndHookMethod(classStatusBarHeaderView, "onFinishInflate", onFinishInflateHook); XposedHelpers.findAndHookMethod(classStatusBarHeaderView, "setExpansion", float.class, setExpansionHook); XposedHelpers.findAndHookMethod(classStatusBarHeaderView, "onConfigurationChanged", Configuration.class, onConfigurationChangedHook); XposedHelpers.findAndHookMethod(classStatusBarHeaderView, "updateVisibilities", updateVisibilitiesHook); try { // Every time you make a typo, the errorists win. XposedHelpers.findAndHookMethod(classLayoutValues, "interpoloate", classLayoutValues, classLayoutValues, float.class, XC_MethodReplacement.DO_NOTHING); } catch (Throwable ignore) { // yeah thx Bliss XposedHelpers.findAndHookMethod(classLayoutValues, "interpolate", classLayoutValues, classLayoutValues, float.class, XC_MethodReplacement.DO_NOTHING); } XposedHelpers.findAndHookMethod(classStatusBarHeaderView, "requestCaptureValues", XC_MethodReplacement.DO_NOTHING); XposedHelpers.findAndHookMethod(classStatusBarHeaderView, "applyLayoutValues", classLayoutValues, XC_MethodReplacement.DO_NOTHING); XposedHelpers.findAndHookMethod(classStatusBarHeaderView, "captureLayoutValues", classLayoutValues, XC_MethodReplacement.DO_NOTHING); XposedHelpers.findAndHookMethod(classStatusBarHeaderView, "updateLayoutValues", float.class, XC_MethodReplacement.DO_NOTHING); XposedHelpers.findAndHookMethod(classStatusBarHeaderView, "updateClockCollapsedMargin", XC_MethodReplacement.DO_NOTHING); XposedHelpers.findAndHookMethod(classStatusBarHeaderView, "updateHeights", XC_MethodReplacement.DO_NOTHING); XposedHelpers.findAndHookMethod(classStatusBarHeaderView, "updateSignalClusterDetachment", XC_MethodReplacement.DO_NOTHING); XposedHelpers.findAndHookMethod(classStatusBarHeaderView, "updateSystemIconsLayoutParams", XC_MethodReplacement.DO_NOTHING); XposedHelpers.findAndHookMethod(classStatusBarHeaderView, "updateAvatarScale", XC_MethodReplacement.DO_NOTHING); XposedHelpers.findAndHookMethod(classStatusBarHeaderView, "updateClockScale", XC_MethodReplacement.DO_NOTHING); XposedHelpers.findAndHookMethod(classStatusBarHeaderView, "updateAmPmTranslation", XC_MethodReplacement.DO_NOTHING); XposedHelpers.findAndHookMethod(classStatusBarHeaderView, "updateClockLp", XC_MethodReplacement.DO_NOTHING); XposedHelpers.findAndHookMethod(classStatusBarHeaderView, "updateMultiUserSwitch", XC_MethodReplacement.DO_NOTHING); XposedHelpers.findAndHookMethod(classStatusBarHeaderView, "setClipping", float.class, XC_MethodReplacement.DO_NOTHING); if (!ConfigUtils.qs().keep_header_background) { try { // AICP test XposedHelpers.findAndHookMethod(classStatusBarHeaderView, "doUpdateStatusBarCustomHeader", Drawable.class, boolean.class, XC_MethodReplacement.DO_NOTHING); } catch (Throwable ignore) { } } XposedHelpers.findAndHookMethod(classStatusBarHeaderView, "onLayout", boolean.class, int.class, int.class, int.class, int.class, new XC_MethodHook() { @Override protected void afterHookedMethod(MethodHookParam param) throws Throwable { mAlarmStatus.setX(0); } }); XposedHelpers.findAndHookMethod(classStatusBarHeaderView, "onClick", View.class, new XC_MethodHook() { @Override protected void afterHookedMethod(MethodHookParam param) throws Throwable { Object v = param.args[0]; if (v == mClock) { try { XposedHelpers.callMethod(param.thisObject, "startClockActivity"); } catch (Throwable ignore) { } } else if (v == mDateCollapsed || v == mDateExpanded) { try { XposedHelpers.callMethod(param.thisObject, "startDateActivity"); } catch (Throwable ignore) { } } } }); try { XposedHelpers.findAndHookMethod(classQSPanel, "fireShowingDetail", CLASS_DETAIL_ADAPTER, new XC_MethodReplacement() { @Override protected Object replaceHookedMethod(MethodHookParam param) throws Throwable { handleShowingDetail(param.args[0]); return null; } }); } catch (Throwable t) { XposedHelpers.findAndHookMethod(classQSPanel, "showDetailAdapter", boolean.class, CLASS_DETAIL_ADAPTER, int[].class, new XC_MethodReplacement() { @Override protected Object replaceHookedMethod(MethodHookParam param) throws Throwable { handleShowingDetail(param.args[0]); return null; } }); } mUseDragPanel = false; try { Class<?> classQSDragPanel = XposedHelpers.findClass(CLASS_QS_DRAG_PANEL, classLoader); XposedHelpers.findAndHookMethod(classQSDragPanel, "setTiles", Collection.class, setTilesHook); XposedHelpers.findAndHookMethod(classQSDragPanel, "setupViews", setupViewsHook); XposedBridge.hookAllMethods(classQSDragPanel, "handleShowDetailImpl", handleShowDetailImplHook); mUseDragPanel = true; } catch (Throwable ignore) { XposedHelpers.findAndHookConstructor(classQSPanel, Context.class, AttributeSet.class, new XC_MethodHook() { @Override protected void afterHookedMethod(MethodHookParam param) throws Throwable { wrapQsDetail((LinearLayout) XposedHelpers.getObjectField(param.thisObject, "mDetail")); } }); XposedBridge.hookAllMethods(classQSPanel, "handleShowDetailImpl", handleShowDetailImplHook); try { XposedHelpers.findAndHookMethod(classQSPanel, "setTiles", Collection.class, setTilesHook); } catch (Throwable t) { // PA XposedHelpers.findAndHookMethod(classQSPanel, "setTiles", setTilesHook); } } try { XposedHelpers.findAndHookMethod(classPhoneStatusBar, "recreateStatusBar", new XC_MethodHook() { @Override protected void beforeHookedMethod(MethodHookParam param) throws Throwable { mRecreatingStatusBar = true; } @Override protected void afterHookedMethod(MethodHookParam param) throws Throwable { mRecreatingStatusBar = false; } }); } catch (Throwable ignore) { } QSTileHostHooks.hook(classLoader); boolean firstRowLarge = ConfigUtils.qs().large_first_row; if (ConfigUtils.qs().new_click_behavior) { final WifiTileHook w = new WifiTileHook(classLoader, (!mUseDragPanel && !firstRowLarge)); final BluetoothTileHook b = new BluetoothTileHook(classLoader, (!mUseDragPanel && !firstRowLarge)); final CellularTileHook c = new CellularTileHook(classLoader); XposedHelpers.findAndHookMethod(classQSTile, "handleLongClick", new XC_MethodHook() { @Override protected void beforeHookedMethod(MethodHookParam param) throws Throwable { Object that = param.thisObject; if (w.maybeHandleLongClick(that) || b.maybeHandleLongClick(that) || c.maybeHandleLongClick(that)) param.setResult(null); } }); } XposedHelpers.findAndHookMethod(classQSTile, "handleStateChanged", handleStateChangedHook); try { // OOS3 XposedHelpers.findAndHookMethod(classQSTileView, "setOverlay", CLASS_QS_TILE + "$Mode", XC_MethodReplacement.DO_NOTHING); } catch (Throwable ignore) { } if (Build.VERSION.SDK_INT >= 22) { XposedHelpers.findAndHookMethod(classQSTileView, "setIcon", ImageView.class, CLASS_QS_STATE, new XC_MethodHook() { boolean forceAnim = false; @Override protected void beforeHookedMethod(MethodHookParam param) throws Throwable { View iv = (View) param.args[0]; Object headerItem = XposedHelpers.getAdditionalInstanceField(param.thisObject, "headerTileRowItem"); forceAnim = headerItem != null && (boolean) headerItem && !Objects.equals(XposedHelpers.getObjectField(param.args[1], "icon"), iv.getTag(iv.getResources().getIdentifier("qs_icon_tag", "id", PACKAGE_SYSTEMUI))); } @Override protected void afterHookedMethod(MethodHookParam param) throws Throwable { if (forceAnim) { View iconView = (View) XposedHelpers.getObjectField(param.thisObject, "mIcon"); if (iconView instanceof ImageView) { Drawable icon = ((ImageView) iconView).getDrawable(); if (icon instanceof Animatable) { if (iconView.isShown()) { ((Animatable) icon).start(); String type = (String) XposedHelpers.getAdditionalInstanceField(param.thisObject, "headerTileRowType"); XposedHook.logD(TAG, "Animating QuickQS icon: " + forceAnim + (type != null ? ("; type: " + type) : "")); } else { ((Animatable) icon).stop(); } } } } } }); } if (ConfigUtils.qs().enable_qs_editor) { XposedHelpers.findAndHookMethod(PACKAGE_SYSTEMUI + ".settings.BrightnessController", classLoader, "updateIcon", boolean.class, new XC_MethodHook() { @Override protected void afterHookedMethod(MethodHookParam param) throws Throwable { View icon = (View) XposedHelpers.getObjectField(param.thisObject, "mIcon"); if (icon != null) icon.setVisibility(View.GONE); } }); } } } catch (Throwable t) { XposedHook.logE(TAG, "Error in hook", t); } } public static int R_string_battery_panel_title; public static void hookResSystemui(XC_InitPackageResources.InitPackageResourcesParam resparam, String modulePath) { try { if (ConfigUtils.qs().header) { XModuleResources modRes = XModuleResources.createInstance(modulePath, resparam.res); XResources.DimensionReplacement zero = new XResources.DimensionReplacement(0, TypedValue.COMPLEX_UNIT_DIP); R_string_battery_panel_title = resparam.res.addResource(modRes, R.string.battery_panel_title); resparam.res.setReplacement(PACKAGE_SYSTEMUI, "dimen", "notification_panel_width", modRes.fwd(R.dimen.notification_panel_width)); resparam.res.setReplacement(PACKAGE_SYSTEMUI, "dimen", "qs_peek_height", zero); resparam.res.setReplacement(PACKAGE_SYSTEMUI, "dimen", "status_bar_header_height", modRes.fwd(R.dimen.status_bar_header_height)); resparam.res.setReplacement(PACKAGE_SYSTEMUI, "dimen", "status_bar_header_height_expanded", modRes.fwd(R.dimen.status_bar_header_height)); resparam.res.setReplacement(PACKAGE_SYSTEMUI, "dimen", "qs_emergency_calls_only_text_size", modRes.fwd(R.dimen.emergency_calls_only_text_size)); resparam.res.setReplacement(PACKAGE_SYSTEMUI, "dimen", "qs_date_collapsed_size", modRes.fwd(R.dimen.date_time_collapsed_size)); resparam.res.setReplacement(PACKAGE_SYSTEMUI, "dimen", "multi_user_avatar_collapsed_size", modRes.fwd(R.dimen.multi_user_avatar_size)); resparam.res.setReplacement(PACKAGE_SYSTEMUI, "dimen", "qs_brightness_padding_top", modRes.fwd(R.dimen.brightness_slider_padding_top)); if (ConfigUtils.M) resparam.res.setReplacement(PACKAGE_SYSTEMUI, "dimen", "multi_user_avatar_expanded_size", modRes.fwd(R.dimen.multi_user_avatar_size)); /* if (!ConfigUtils.qs().large_first_row) { try { resparam.res.setReplacement(PACKAGE_SYSTEMUI, "dimen", "qs_dual_tile_height", new XResources.DimensionReplacement(resparam.res.getDimensionPixelSize( resparam.res.getIdentifier("qs_tile_height", "dimen", PACKAGE_SYSTEMUI)), TypedValue.COMPLEX_UNIT_PX)); resparam.res.setReplacement(PACKAGE_SYSTEMUI, "dimen", "qs_tile_divider_height", zero); } catch (Throwable t) { XposedHook.logE(TAG, "Couldn't change qs_dual_tile_height or qs_tile_divider_height (" + t.getClass().getSimpleName() + ")", null); } } */ resparam.res.setReplacement(PACKAGE_SYSTEMUI, "color", "qs_tile_divider", 0x00000000); resparam.res.hookLayout(PACKAGE_SYSTEMUI, "layout", "status_bar_expanded_header", new XC_LayoutInflated() { @Override public void handleLayoutInflated(LayoutInflatedParam liparam) throws Throwable { liparam.view.setElevation(0); liparam.view.setPadding(0, 0, 0, 0); FrameLayout.LayoutParams params = (FrameLayout.LayoutParams) liparam.view.getLayoutParams(); params.height = ResourceUtils.getInstance(liparam.view.getContext()).getDimensionPixelSize(R.dimen.status_bar_header_height); } }); // Motorola try { resparam.res.hookLayout(PACKAGE_SYSTEMUI, "layout", "zz_moto_status_bar_expanded_header", new XC_LayoutInflated() { @Override public void handleLayoutInflated(LayoutInflatedParam liparam) throws Throwable { liparam.view.setElevation(0); liparam.view.setPadding(0, 0, 0, 0); FrameLayout.LayoutParams params = (FrameLayout.LayoutParams) liparam.view.getLayoutParams(); params.height = ResourceUtils.getInstance(liparam.view.getContext()).getDimensionPixelSize(R.dimen.status_bar_header_height); } }); } catch (Throwable ignore) { } resparam.res.hookLayout(PACKAGE_SYSTEMUI, "layout", "qs_detail_header", new XC_LayoutInflated() { @Override public void handleLayoutInflated(LayoutInflatedParam liparam) throws Throwable { try { LinearLayout layout = (LinearLayout) liparam.view; Context context = layout.getContext(); ResourceUtils res = ResourceUtils.getInstance(context); LayoutInflater inflater = (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE); int padding = context.getResources().getDimensionPixelSize(context.getResources().getIdentifier("qs_panel_padding", "dimen", PACKAGE_SYSTEMUI)); TextView title = (TextView) layout.findViewById(android.R.id.title); title.setPadding(padding, 0, 0, 0); mQsRightButton = (ImageView) inflater.inflate(res.getLayout(R.layout.qs_right_button), null); mQsRightButton.setOnClickListener(onClickListener); mQsRightButton.setVisibility(View.GONE); layout.addView(mQsRightButton); layout.setPadding(0, 0, padding, 0); layout.setGravity(Gravity.CENTER); } catch (Throwable ignore) { } } }); resparam.res.hookLayout(PACKAGE_SYSTEMUI, "layout", "qs_panel", new XC_LayoutInflated() { @Override public void handleLayoutInflated(LayoutInflatedParam liparam) throws Throwable { FrameLayout layout = (FrameLayout) liparam.view; Context context = layout.getContext(); mQsContainer = layout; layout.setElevation(ResourceUtils.getInstance(context).getDimensionPixelSize(R.dimen.qs_container_elevation)); LinearLayout.LayoutParams params = (LinearLayout.LayoutParams) layout.getLayoutParams(); params.setMargins(0, 0, 0, 0); params.setMarginStart(0); params.setMarginEnd(0); layout.setLayoutParams(params); mQsPanel = (ViewGroup) layout.findViewById(context.getResources().getIdentifier("quick_settings_panel", "id", PACKAGE_SYSTEMUI)); } }); /*resparam.res.hookLayout(PACKAGE_SYSTEMUI, "layout", "status_bar_expanded", new XC_LayoutInflated() { @Override public void handleLayoutInflated(LayoutInflatedParam liparam) throws Throwable { View view = liparam.view; Context context = view.getContext(); int scrollViewId = context.getResources().getIdentifier("scroll_view", "id", PACKAGE_SYSTEMUI); int containerId = context.getResources().getIdentifier("notification_container_parent", "id", PACKAGE_SYSTEMUI); ViewGroup container = (ViewGroup) view.findViewById(containerId); ViewGroup mNotificationPanelView = (ViewGroup)container.getParent(); ViewGroup scrollView = (ViewGroup) container.findViewById(scrollViewId); LinearLayout linearLayout = (LinearLayout) scrollView.getChildAt(0); FrameLayout frameLayout = new FrameLayout(context); frameLayout.setLayoutParams(new FrameLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT)); linearLayout.removeViewAt(0); scrollView.removeView(linearLayout); container.removeView(scrollView); container.addView(scrollView); container.addView(frameLayout,0); frameLayout.addView(mQsContainer); mQsContainer.setY(0); container.setLayoutParams(scrollView.getLayoutParams()); mNotificationPanelView.removeView(mStatusBarHeaderView); mQsContainer.addView(mStatusBarHeaderView,1); mQsContainer.setVisibility(View.VISIBLE); //linearLayout.removeView(mQsContainer); //scrollView.setVisibility(View.GONE); //container.addView(mQsContainer,0); //container.setLayoutParams(scrollView.getLayoutParams()); } });*/ } } catch (Throwable t) { XposedHook.logE(TAG, "Error hooking SystemUI resources", t); } } public static QuickQSPanel getHeaderQsPanel() { return mHeaderQsPanel; } public static void createQsAnimator() { mQsAnimator = new QSAnimator(mQsContainer, mHeaderQsPanel, mQsPanel); } public static View getSettingsButton() { return mSettingsButton; } }