package tk.wasdennnoch.androidn_ify.systemui.notifications; import android.annotation.SuppressLint; import android.app.Dialog; import android.app.Notification; import android.app.PendingIntent; import android.app.RemoteInput; import android.content.Context; import android.content.pm.PackageManager; import android.content.res.ColorStateList; import android.content.res.Resources; import android.content.res.XModuleResources; import android.content.res.XResources; import android.graphics.Bitmap; import android.graphics.Color; import android.graphics.PorterDuff; import android.graphics.Typeface; import android.graphics.drawable.ColorDrawable; import android.graphics.drawable.Drawable; import android.graphics.drawable.GradientDrawable; import android.graphics.drawable.Icon; import android.graphics.drawable.RippleDrawable; import android.graphics.drawable.ShapeDrawable; import android.os.Build; import android.os.Bundle; import android.os.SystemClock; import android.service.notification.StatusBarNotification; import android.support.annotation.NonNull; import android.support.v4.app.NotificationCompat; import android.support.v4.graphics.ColorUtils; import android.text.TextUtils; 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.ViewStub; import android.view.Window; import android.view.WindowManager; import android.widget.Button; import android.widget.DateTimeView; import android.widget.FrameLayout; import android.widget.ImageButton; import android.widget.ImageView; import android.widget.LinearLayout; import android.widget.RelativeLayout; import android.widget.RemoteViews; import android.widget.TextView; import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Map; 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.FakeShadowView; import tk.wasdennnoch.androidn_ify.extracted.systemui.NotificationActionListLayout; import tk.wasdennnoch.androidn_ify.extracted.systemui.RemoteInputView; import tk.wasdennnoch.androidn_ify.misc.SafeOnClickListener; import tk.wasdennnoch.androidn_ify.systemui.SystemUIHooks; import tk.wasdennnoch.androidn_ify.systemui.notifications.stack.NotificationStackScrollLayoutHooks; import tk.wasdennnoch.androidn_ify.systemui.notifications.views.RemoteInputHelper; import tk.wasdennnoch.androidn_ify.systemui.qs.customize.QSCustomizer; import tk.wasdennnoch.androidn_ify.systemui.statusbar.StatusBarHooks; import tk.wasdennnoch.androidn_ify.utils.ConfigUtils; import tk.wasdennnoch.androidn_ify.utils.RemoteMarginLinearLayout; import tk.wasdennnoch.androidn_ify.utils.ResourceUtils; import tk.wasdennnoch.androidn_ify.utils.RomUtils; import tk.wasdennnoch.androidn_ify.utils.ViewUtils; import static android.view.ViewGroup.LayoutParams.MATCH_PARENT; import static android.view.ViewGroup.LayoutParams.WRAP_CONTENT; import static de.robv.android.xposed.XposedHelpers.getObjectField; @SuppressLint("StaticFieldLeak") public class NotificationHooks { private static final String TAG = "NotificationHooks"; private static final String PACKAGE_ANDROID = XposedHook.PACKAGE_ANDROID; private static final String PACKAGE_SYSTEMUI = XposedHook.PACKAGE_SYSTEMUI; private static final String KEY_EXPAND_CLICK_LISTENER = "expandClickListener"; public static final String EXTRA_SUBSTITUTE_APP_NAME = "nify.substName"; private static int mNotificationBgColor; private static int mNotificationBgDimmedColor; private static int mAccentColor = 0; private static final Map<String, Integer> mGeneratedColors = new HashMap<>(); private static Object mPhoneStatusBar; public static boolean remoteInputActive = false; public static Object statusBarWindowManager = null; public static NotificationStackScrollLayoutHooks mStackScrollLayoutHooks; private static SensitiveNotificationFilter mSensitiveFilter = new SensitiveNotificationFilter(); private static final XC_MethodHook inflateViewsHook = new XC_MethodHook() { @SuppressWarnings({"deprecation", "UnusedAssignment"}) @Override protected void afterHookedMethod(MethodHookParam param) throws Throwable { if (!(boolean) param.getResult()) return; Object entry = param.args[0]; Object row = XposedHelpers.getObjectField(entry, "row"); Object contentContainer = XposedHelpers.getObjectField(row, "mPrivateLayout"); // NotificationContentView Object contentContainerPublic = XposedHelpers.getObjectField(row, "mPublicLayout"); ConfigUtils.notifications().loadBlacklistedApps(); StatusBarNotification sbn = (StatusBarNotification) XposedHelpers.callMethod(row, "getStatusBarNotification"); if (ConfigUtils.notifications().blacklistedApps.contains(sbn.getPackageName())) return; View privateView = (View) XposedHelpers.callMethod(contentContainer, "getContractedChild"); View publicView = (View) XposedHelpers.callMethod(contentContainerPublic, "getContractedChild"); Context context = publicView.getContext(); // Try to find app label for notifications without public version TextView appName = (TextView) publicView.findViewById(R.id.public_app_name_text); if (appName == null) { // For notifications with public version appName = (TextView) publicView.findViewById(R.id.app_name_text); } View time = publicView.findViewById(context.getResources().getIdentifier("time", "id", PACKAGE_SYSTEMUI)); if (time != null) { publicView.findViewById(R.id.public_time_divider).setVisibility(time.getVisibility()); } // Try to find icon for notifications without public version ImageView icon = (ImageView) publicView.findViewById(context.getResources().getIdentifier("icon", "id", PACKAGE_SYSTEMUI)); if (icon == null) { // For notifications with public version icon = (ImageView) publicView.findViewById(R.id.notification_icon); } if (icon == null) { icon = (ImageView) publicView.findViewById(android.R.id.icon); } if (icon != null) { icon.setBackgroundResource(0); icon.setBackgroundColor(0x00000000); icon.setPadding(0, 0, 0, 0); } TextView privateAppName = (TextView) privateView.findViewById(R.id.app_name_text); int color = privateAppName != null ? privateAppName.getTextColors().getDefaultColor() : sbn.getNotification().color; if (privateAppName != null) { if (appName != null) { appName.setTextColor(privateAppName.getTextColors()); appName.setText(privateAppName.getText()); } if (icon != null) { icon.setColorFilter(color, PorterDuff.Mode.SRC_ATOP); } } // actions background View expandedChild = (View) XposedHelpers.callMethod(contentContainer, "getExpandedChild"); View headsUpChild = ConfigUtils.M ? (View) XposedHelpers.callMethod(contentContainer, "getHeadsUpChild") : null; if (!ConfigUtils.notifications().custom_actions_color) { if (expandedChild != null || headsUpChild != null) { int actionsId = context.getResources().getIdentifier("actions", "id", PACKAGE_ANDROID); double[] lab = new double[3]; ColorUtils.colorToLAB(mNotificationBgColor, lab); lab[0] = 1.0f - 0.95f * (1.0f - lab[0]); int endColor = ColorUtils.setAlphaComponent(ColorUtils.LABToColor(lab[0], lab[1], lab[2]), Color.alpha(mNotificationBgColor)); if (expandedChild != null) { View actionsExpanded = expandedChild.findViewById(actionsId); if (actionsExpanded != null) { actionsExpanded.setBackgroundColor(endColor); } } if (headsUpChild != null) { View actionsHeadsUp = headsUpChild.findViewById(actionsId); if (actionsHeadsUp != null) { actionsHeadsUp.setBackgroundColor(endColor); } } } } if (RemoteInputHelper.DIRECT_REPLY_ENABLED) { Notification.Action[] actions = sbn.getNotification().actions; if (actions != null) { addRemoteInput(context, expandedChild, actions, color, null, null); if (ConfigUtils.M) addRemoteInput(context, headsUpChild, actions, color, getObjectField(param.thisObject, "mHeadsUpManager"), (String) getObjectField(entry, "key")); } } } }; private static void addRemoteInput(Context context, View child, Notification.Action[] actions, int color, final Object headsUpManager, final String key) { if (child == null) { return; } NotificationActionListLayout actionsLayout = (NotificationActionListLayout) child.findViewById(context.getResources().getIdentifier("actions", "id", PACKAGE_ANDROID)); if (actionsLayout == null) { return; } FrameLayout actionContainer = new FrameLayout(context); // Transfer views int startMargin = ((ViewGroup.MarginLayoutParams) actionsLayout.getLayoutParams()).getMarginStart(); ViewGroup parent = (ViewGroup) actionsLayout.getParent(); parent.removeView(actionsLayout); parent.addView(actionContainer, MATCH_PARENT, WRAP_CONTENT); actionContainer.addView(actionsLayout); ViewUtils.setMarginStart(actionsLayout, startMargin); // Add remote input if (haveRemoteInput(actions)) { LinearLayout riv = RemoteInputView.inflate(context, actionContainer); riv.setVisibility(View.INVISIBLE); actionContainer.addView(riv, new FrameLayout.LayoutParams( MATCH_PARENT, MATCH_PARENT) ); riv.setBackgroundColor(color); } for (int i = 0; i < actions.length; i++) { final Notification.Action action = actions[i]; if (actions[i].getRemoteInputs() != null) { Button actionButton = (Button) actionsLayout.getChildAt(i); final View.OnClickListener old = (View.OnClickListener) getObjectField(XposedHelpers.callMethod(actionButton, "getListenerInfo"), "mOnClickListener"); actionButton.setOnClickListener(new View.OnClickListener() { @Override public void onClick(final View view) { if (ConfigUtils.notifications().allow_direct_reply_on_keyguard) { handleRemoteInput(view); } else { SystemUIHooks.startRunnableDismissingKeyguard(new Runnable() { @Override public void run() { SystemUIHooks.post(new Runnable() { @Override public void run() { handleRemoteInput(view); } }); } }); } } private void handleRemoteInput(View view) { Object headsUpEntry = headsUpManager != null ? XposedHelpers.callMethod(headsUpManager, "getHeadsUpEntry", key) : null; if (!RemoteInputHelper.handleRemoteInput(view, action.actionIntent, action.getRemoteInputs(), headsUpEntry)) { old.onClick(view); } } }); } } } private static final XC_MethodHook getStandardViewHook = new XC_MethodHook() { @Override protected void afterHookedMethod(MethodHookParam param) throws Throwable { RemoteViews contentView = (RemoteViews) param.getResult(); Notification.Builder mBuilder = (Notification.Builder) XposedHelpers.getObjectField(param.thisObject, "mBuilder"); CharSequence overflowText = (CharSequence) (XposedHelpers.getBooleanField(param.thisObject, "mSummaryTextSet") ? XposedHelpers.getObjectField(param.thisObject, "mSummaryText") : XposedHelpers.getObjectField(mBuilder, "mSubText")); if (overflowText != null) { contentView.setTextViewText(R.id.header_text, processLegacyText(mBuilder, overflowText)); contentView.setViewVisibility(R.id.header_text_divider, View.VISIBLE); contentView.setViewVisibility(R.id.header_text, View.VISIBLE); } } }; private static CharSequence processLegacyText(Object notifBuilder, CharSequence text) { try { return (CharSequence) XposedHelpers.callMethod(notifBuilder, "processLegacyText", text); } catch (Throwable t) { return (CharSequence) XposedHelpers.callMethod(notifBuilder, "processText", XposedHelpers.callMethod(notifBuilder, "getTextColor", 255), text); } } private static void bindNotificationHeader(RemoteViews contentView, XC_MethodHook.MethodHookParam param) { Object builder = param.thisObject; Context context = (Context) XposedHelpers.getObjectField(builder, "mContext"); Bundle extras = (Bundle) XposedHelpers.getObjectField(builder, "mExtras"); int color = resolveColor(builder); bindSmallIcon(contentView, builder, color); bindHeaderAppName(contentView, context, color, extras); bindHeaderText(contentView, builder, context); bindHeaderChronometerAndTime(contentView, builder); bindExpandButton(contentView, color); } private static void bindHeaderChronometerAndTime(RemoteViews contentView, Object builder) { long mWhen = XposedHelpers.getLongField(builder, "mWhen"); if ((boolean) XposedHelpers.callMethod(builder, "showsTimeOrChronometer")) { contentView.setViewVisibility(R.id.time_divider, View.VISIBLE); if (XposedHelpers.getBooleanField(builder, "mUseChronometer")) { contentView.setViewVisibility(R.id.chronometer, View.VISIBLE); contentView.setLong(R.id.chronometer, "setBase", mWhen + (SystemClock.elapsedRealtime() - System.currentTimeMillis())); contentView.setBoolean(R.id.chronometer, "setStarted", true); } else { contentView.setViewVisibility(R.id.time, View.VISIBLE); contentView.setLong(R.id.time, "setTime", mWhen); } } else { contentView.setLong(R.id.time, "setTime", mWhen); } } private static void bindHeaderText(RemoteViews contentView, Object builder, Context context) { CharSequence mContentText = (CharSequence) XposedHelpers.getObjectField(builder, "mContentText"); CharSequence mSubText = (CharSequence) XposedHelpers.getObjectField(builder, "mSubText"); if (mContentText != null && mSubText != null) { contentView.setTextViewText(context.getResources().getIdentifier("text", "id", PACKAGE_ANDROID), processLegacyText(builder, mContentText)); contentView.setViewVisibility(context.getResources().getIdentifier("text2", "id", PACKAGE_ANDROID), View.GONE); contentView.setTextViewText(R.id.header_text, processLegacyText(builder, mSubText)); contentView.setViewVisibility(R.id.header_text, View.VISIBLE); contentView.setViewVisibility(R.id.header_text_divider, View.VISIBLE); } try { // TODO Why it's crashing here XposedHelpers.callMethod(builder, "unshrinkLine3Text"); } catch (Throwable ignore) { } } private static void bindSmallIcon(RemoteViews contentView, Object builder, int color) { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { Icon mSmallIcon = (Icon) XposedHelpers.getObjectField(builder, "mSmallIcon"); contentView.setImageViewIcon(R.id.icon, mSmallIcon); } else { int mSmallIcon = XposedHelpers.getIntField(builder, "mSmallIcon"); contentView.setImageViewResource(R.id.icon, mSmallIcon); } processSmallIconColor(contentView, builder, color); } private static void bindHeaderAppName(RemoteViews contentView, Context context, int color, Bundle extras) { contentView.setTextViewText(R.id.app_name_text, loadHeaderAppName(context, extras)); contentView.setTextColor(R.id.app_name_text, color); } private static void processSmallIconColor(RemoteViews contentView, Object builder, int resolveColor) { boolean colorable = true; int color = NotificationHeaderView.NO_COLOR; Context context = (Context) XposedHelpers.getObjectField(builder, "mContext"); boolean legacy = false; try { legacy = (boolean) XposedHelpers.callMethod(builder, "isLegacy"); } catch (Throwable ignore) { } if (legacy) { Object mColorUtil = XposedHelpers.getObjectField(builder, "mColorUtil"); Object mSmallIcon = XposedHelpers.getObjectField(builder, "mSmallIcon"); // Icon if Marshmallow, int if Lollipop. So we shouldn't specify which type is this. if (!(boolean) XposedHelpers.callMethod(mColorUtil, "isGrayscaleIcon", context, mSmallIcon)) { colorable = false; } } if (colorable) { color = resolveColor; XposedHelpers.callMethod(contentView, "setDrawableParameters", R.id.icon, false, -1, color, PorterDuff.Mode.SRC_ATOP, -1); } contentView.setInt(R.id.notification_header, "setOriginalIconColor", color); } private static void bindExpandButton(RemoteViews contentView, int color) { XposedHelpers.callMethod(contentView, "setDrawableParameters", R.id.expand_button, false, -1, color, PorterDuff.Mode.SRC_ATOP, -1); contentView.setInt(R.id.notification_header, "setOriginalIconColor", color); } private static int resolveColor(Object builder) { return (int) XposedHelpers.callMethod(builder, "resolveColor"); } private static String loadHeaderAppName(Context context, Bundle extras) { if (extras != null && extras.containsKey(EXTRA_SUBSTITUTE_APP_NAME)) { // TODO why this doesn't work final String pkg = context.getPackageName(); final String subName = extras.getString(EXTRA_SUBSTITUTE_APP_NAME); if (pkg.equals(XposedHook.PACKAGE_OWN)) { return subName; } } CharSequence appname = context.getPackageName(); if (appname.equals(PACKAGE_SYSTEMUI)) return context.getString(context.getResources().getIdentifier("android_system_label", "string", PACKAGE_ANDROID)); try { appname = context.getString(context.getApplicationInfo().labelRes); } catch (Throwable t) { try { appname = context.getApplicationInfo().loadLabel(context.getPackageManager()); } catch (Throwable ignore) { } } return String.valueOf(appname); } private static void handleProgressBar(boolean hasProgress, RemoteViews contentView, Object builder, Resources res) { final int max = XposedHelpers.getIntField(builder, "mProgressMax"); final boolean ind = XposedHelpers.getBooleanField(builder, "mProgressIndeterminate"); if (hasProgress && (max != 0 || ind)) { CharSequence text = (CharSequence) XposedHelpers.getObjectField(builder, "mContentText"); contentView.setTextViewText(R.id.text_line_1, text); contentView.setViewVisibility(R.id.text_line_1, View.VISIBLE); contentView.setViewVisibility(res.getIdentifier("line3", "id", PACKAGE_ANDROID), View.GONE); } } private static final XC_MethodHook applyStandardTemplateHook = new XC_MethodHook() { @Override protected void afterHookedMethod(MethodHookParam param) throws Throwable { Object builder = param.thisObject; Context context = (Context) XposedHelpers.getObjectField(builder, "mContext"); Resources res = context.getResources(); RemoteViews contentView = (RemoteViews) param.getResult(); bindNotificationHeader(contentView, param); if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { Icon mLargeIcon = (Icon) XposedHelpers.getObjectField(builder, "mLargeIcon"); contentView.setImageViewIcon(res.getIdentifier("right_icon", "id", "android"), mLargeIcon); } else { Bitmap mLargeIcon = (Bitmap) XposedHelpers.getObjectField(builder, "mLargeIcon"); contentView.setImageViewBitmap(res.getIdentifier("right_icon", "id", PACKAGE_ANDROID), mLargeIcon); } if (XposedHelpers.getObjectField(builder, "mLargeIcon") != null) { int notificationTextMarginEnd = R.dimen.notification_text_margin_end; contentView.setInt(R.id.line1_container, "setMarginEnd", notificationTextMarginEnd); contentView.setInt(R.id.line2_container, "setMarginEnd", notificationTextMarginEnd); contentView.setInt(R.id.line3_container, "setMarginEnd", notificationTextMarginEnd); } handleProgressBar((boolean) param.args[1], contentView, builder, res); contentView.setInt(res.getIdentifier("right_icon", "id", "android"), "setBackgroundResource", 0); } }; private static final XC_MethodHook makeMediaContentViewHook = new XC_MethodHook() { @Override protected void afterHookedMethod(MethodHookParam param) throws Throwable { Context context = (Context) XposedHelpers.getObjectField(XposedHelpers.getObjectField(param.thisObject, "mBuilder"), "mContext"); RemoteViews view = (RemoteViews) param.getResult(); int endMargin = R.dimen.notification_content_margin_end; if (XposedHelpers.getObjectField(XposedHelpers.getObjectField(param.thisObject, "mBuilder"), "mLargeIcon") != null) { endMargin = R.dimen.notification_content_plus_picture_margin_end; view.setInt(R.id.line1_container, "setMarginEnd", R.dimen.zero); view.setInt(R.id.line2_container, "setMarginEnd", R.dimen.zero); view.setInt(R.id.line3_container, "setMarginEnd", R.dimen.zero); } view.setInt(context.getResources().getIdentifier("notification_main_column", "id", "android"), "setMarginEnd", endMargin); } }; private static XC_MethodHook generateMediaActionButtonHook = new XC_MethodHook() { @Override protected void afterHookedMethod(MethodHookParam param) throws Throwable { Context context = (Context) XposedHelpers.getObjectField(XposedHelpers.getObjectField(param.thisObject, "mBuilder"), "mContext"); RemoteViews button = (RemoteViews) param.getResult(); XposedHelpers.callMethod(button, "setDrawableParameters", context.getResources().getIdentifier("action0", "id", PACKAGE_ANDROID), false, -1, XposedHelpers.callMethod(XposedHelpers.getObjectField(param.thisObject, "mBuilder"), "resolveColor"), PorterDuff.Mode.SRC_ATOP, -1); } }; private static final XC_MethodHook resetStandardTemplateHook = new XC_MethodHook() { @Override protected void afterHookedMethod(MethodHookParam param) throws Throwable { RemoteViews contentView = (RemoteViews) param.args[0]; contentView.setImageViewResource(R.id.icon, 0); contentView.setTextViewText(R.id.app_name_text, null); contentView.setViewVisibility(R.id.chronometer, View.GONE); contentView.setViewVisibility(R.id.header_text, View.GONE); contentView.setTextViewText(R.id.header_text, null); contentView.setViewVisibility(R.id.header_text_divider, View.GONE); contentView.setViewVisibility(R.id.time_divider, View.GONE); contentView.setViewVisibility(R.id.time, View.GONE); contentView.setViewVisibility(R.id.text_line_1, View.GONE); contentView.setTextViewText(R.id.text_line_1, null); } }; private static final XC_MethodHook processSmallIconAsLargeHook = new XC_MethodReplacement() { @Override protected Object replaceHookedMethod(MethodHookParam methodHookParam) throws Throwable { boolean legacy = false; try { legacy = ((boolean) XposedHelpers.callMethod(methodHookParam.thisObject, "isLegacy")); } catch (Throwable ignore) { } if (!legacy) { RemoteViews contentView = (RemoteViews) methodHookParam.args[1]; int mColor = (int) XposedHelpers.callMethod(methodHookParam.thisObject, "resolveColor"); XposedHelpers.callMethod(contentView, "setDrawableParameters", android.R.id.icon, false, -1, mColor, PorterDuff.Mode.SRC_ATOP, -1); } return null; } }; private static final XC_MethodHook applyStandardTemplateWithActionsHook = new XC_MethodHook() { @SuppressWarnings("unchecked") @Override protected void afterHookedMethod(MethodHookParam param) throws Throwable { Context context = (Context) XposedHelpers.getObjectField(param.thisObject, "mContext"); RemoteViews big = (RemoteViews) param.getResult(); big.setViewVisibility(context.getResources().getIdentifier("action_divider", "id", PACKAGE_ANDROID), View.GONE); ArrayList<Notification.Action> mActions = (ArrayList<Notification.Action>) XposedHelpers.getObjectField(param.thisObject, "mActions"); big.setViewVisibility(R.id.actions_container, mActions.size() > 0 ? View.VISIBLE : View.GONE); } }; private static final XC_MethodHook generateActionButtonHook = new XC_MethodHook() { @Override protected void afterHookedMethod(MethodHookParam param) throws Throwable { Context context = (Context) XposedHelpers.getObjectField(param.thisObject, "mContext"); int mColor = (int) XposedHelpers.callMethod(param.thisObject, "resolveColor"); int textViewId = context.getResources().getIdentifier("action0", "id", PACKAGE_ANDROID); Notification.Action action = (Notification.Action) param.args[0]; RemoteViews button = (RemoteViews) param.getResult(); if (action.title != null && action.title.length() != 0) { button.setTextViewCompoundDrawablesRelative(textViewId, 0, 0, 0, 0); button.setTextColor(textViewId, mColor); } else { XposedHelpers.callMethod(button, "setTextViewCompoundDrawablesRelativeColorFilter", textViewId, 0, mColor, PorterDuff.Mode.SRC_ATOP); } } }; private static final XC_MethodReplacement resolveColorHook = new XC_MethodReplacement() { @Override protected Object replaceHookedMethod(MethodHookParam param) throws Throwable { int mColor = XposedHelpers.getIntField(param.thisObject, "mColor"); if (mColor != 0) return mColor; // App specified color in notification builder Context context = (Context) XposedHelpers.getObjectField(param.thisObject, "mContext"); if (mAccentColor == 0) { //noinspection deprecation mAccentColor = context.getResources().getColor(context.getResources().getIdentifier("notification_icon_bg_color", "color", PACKAGE_ANDROID)); } int c = mAccentColor; if (ConfigUtils.notifications().generate_notification_accent_color) { String packageName = context.getPackageName(); if (mGeneratedColors.containsKey(packageName)) return mGeneratedColors.get(packageName); try { Drawable appIcon = context.getPackageManager().getApplicationIcon(packageName); c = tk.wasdennnoch.androidn_ify.utils.ColorUtils.generateColor(appIcon, mAccentColor); mGeneratedColors.put(packageName, c); } catch (PackageManager.NameNotFoundException ignore) { } } return c; } }; private static final XC_MethodHook buildHook = new XC_MethodHook() { @Override protected void beforeHookedMethod(MethodHookParam param) throws Throwable { ConfigUtils.notifications().loadBlacklistedApps(); String name = ((Context) getObjectField(param.thisObject, "mContext")).getPackageName(); if (!RemoteInputHelper.DIRECT_REPLY_ENABLED || ConfigUtils.notifications().blacklistedApps.contains(name)) { return; } Notification.Builder b = (Notification.Builder) param.thisObject; @SuppressWarnings("unchecked") List<Notification.Action> actions = (List<Notification.Action>) getObjectField(b, "mActions"); if (!actions.isEmpty() && haveRemoteInput(actions.toArray(new Notification.Action[actions.size()]))) { return; } final List<Notification.Action> wearableRemoteInputActions = new ArrayList<>(); final String EXTRA_WEARABLE_EXTENSIONS = (String) XposedHelpers.getStaticObjectField(Notification.WearableExtender.class, "EXTRA_WEARABLE_EXTENSIONS"); final String KEY_ACTIONS = (String) XposedHelpers.getStaticObjectField(Notification.WearableExtender.class, "KEY_ACTIONS"); Bundle wearableBundle = b.getExtras().getBundle(EXTRA_WEARABLE_EXTENSIONS); if (wearableBundle != null) { ArrayList<Notification.Action> wearableActions = wearableBundle.getParcelableArrayList(KEY_ACTIONS); if (wearableActions != null) { for (int i = 0; i < wearableActions.size(); i++) { if (hasValidRemoteInput(wearableActions.get(i))) { wearableRemoteInputActions.add(wearableActions.get(i)); } } } } if (wearableRemoteInputActions.size() > 0) { actions.addAll(0, wearableRemoteInputActions); return; } try { RemoteInput carRemoteInput = null; PendingIntent carReplyPendingIntent = null; final String EXTRA_CAR_EXTENDER = (String) XposedHelpers.getStaticObjectField(Notification.CarExtender.class, "EXTRA_CAR_EXTENDER"); final String EXTRA_CONVERSATION = (String) XposedHelpers.getStaticObjectField(Notification.CarExtender.class, "EXTRA_CONVERSATION"); final String KEY_REMOTE_INPUT = (String) XposedHelpers.getStaticObjectField(Notification.CarExtender.UnreadConversation.class, "KEY_REMOTE_INPUT"); final String KEY_ON_REPLY = (String) XposedHelpers.getStaticObjectField(Notification.CarExtender.UnreadConversation.class, "KEY_ON_REPLY"); Bundle carBundle = b.getExtras().getBundle(EXTRA_CAR_EXTENDER); if (carBundle != null) { Bundle unreadConversation = carBundle.getBundle(EXTRA_CONVERSATION); if (unreadConversation != null) { carRemoteInput = unreadConversation.getParcelable(KEY_REMOTE_INPUT); carReplyPendingIntent = unreadConversation.getParcelable(KEY_ON_REPLY); } } if (carRemoteInput != null && carReplyPendingIntent != null) { //noinspection deprecation actions.add(0, new Notification.Action.Builder(0, "Reply", carReplyPendingIntent).addRemoteInput(carRemoteInput).build()); } } catch (NoClassDefFoundError e) { // Ignore } catch (Throwable t) { XposedHook.logE(TAG, "Error in buildHook (car extender)", t); } } }; private static final XC_MethodHook calculateTopPaddingHook = new XC_MethodHook() { @Override protected void beforeHookedMethod(MethodHookParam param) throws Throwable { param.args[1] = false; } }; private static final XC_MethodHook initConstantsHook = new XC_MethodHook() { @Override protected void afterHookedMethod(MethodHookParam param) throws Throwable { XposedHelpers.setBooleanField(param.thisObject, "mScaleDimmed", false); } }; private static final XC_MethodHook updateWindowWidthHHook = new XC_MethodHook() { @Override protected void afterHookedMethod(MethodHookParam param) throws Throwable { if (RomUtils.isOneplusStock()) { return; } Dialog mDialog = (Dialog) XposedHelpers.getObjectField(param.thisObject, "mDialog"); ViewGroup mDialogView = (ViewGroup) XposedHelpers.getObjectField(param.thisObject, "mDialogView"); Context context = mDialogView.getContext(); Window window = mDialog.getWindow(); ViewGroup.MarginLayoutParams lp = (ViewGroup.MarginLayoutParams) mDialogView.getLayoutParams(); lp.setMargins(0, lp.topMargin, 0, lp.bottomMargin); mDialogView.setLayoutParams(lp); //noinspection deprecation mDialogView.setBackgroundColor(context.getResources().getColor(context.getResources().getIdentifier("system_primary_color", "color", PACKAGE_SYSTEMUI))); mDialogView.requestLayout(); // Required to apply the new margin assert window != null; WindowManager.LayoutParams wlp = window.getAttributes(); wlp.horizontalMargin = 0; window.setAttributes(wlp); } }; private static final XC_MethodHook updateWidthHook = new XC_MethodHook() { @Override protected void afterHookedMethod(MethodHookParam param) throws Throwable { Dialog mDialog = (Dialog) XposedHelpers.getObjectField(param.thisObject, "mDialog"); Resources res = mDialog.getContext().getResources(); Window window = mDialog.getWindow(); assert window != null; WindowManager.LayoutParams wlp = window.getAttributes(); wlp.horizontalMargin = 0; window.setAttributes(wlp); ViewGroup mContentParent = (ViewGroup) XposedHelpers.getObjectField(window, "mContentParent"); ViewGroup panel = (ViewGroup) mContentParent.findViewById(res.getIdentifier("visible_panel", "id", PACKAGE_SYSTEMUI)); ViewGroup dialogView = (ViewGroup) panel.getParent(); ViewGroup.MarginLayoutParams lp = (ViewGroup.MarginLayoutParams) dialogView.getLayoutParams(); lp.setMargins(0, lp.topMargin, 0, lp.bottomMargin); //noinspection deprecation dialogView.setBackgroundColor(res.getColor(res.getIdentifier("system_primary_color", "color", PACKAGE_SYSTEMUI))); dialogView.requestLayout(); // Required to apply the new margin } }; private static final XC_MethodHook dismissViewButtonConstructorHook = new XC_MethodHook() { @Override protected void afterHookedMethod(MethodHookParam param) throws Throwable { if (param.thisObject instanceof TextView) { TextView button = (TextView) param.thisObject; // It's a TextView on some ROMs Drawable mAnimatedDismissDrawable = (Drawable) XposedHelpers.getObjectField(param.thisObject, "mAnimatedDismissDrawable"); mAnimatedDismissDrawable.setBounds(0, 0, 0, 0); Drawable mStaticDismissDrawable = (Drawable) XposedHelpers.getObjectField(param.thisObject, "mStaticDismissDrawable"); mStaticDismissDrawable.setBounds(0, 0, 0, 0); button.setVisibility(View.VISIBLE); } } }; public static void hookResSystemui(XC_InitPackageResources.InitPackageResourcesParam resparam, String modulePath) { try { ConfigUtils config = ConfigUtils.getInstance(); mSensitiveFilter.hookRes(resparam); final XModuleResources modRes = XModuleResources.createInstance(modulePath, resparam.res); XResources.DimensionReplacement zero = new XResources.DimensionReplacement(0, TypedValue.COMPLEX_UNIT_DIP); if (config.notifications.change_style) { resparam.res.setReplacement(PACKAGE_SYSTEMUI, "dimen", "notification_side_padding", zero); resparam.res.setReplacement(PACKAGE_SYSTEMUI, "dimen", "notifications_top_padding", zero); resparam.res.setReplacement(PACKAGE_SYSTEMUI, "dimen", "notification_padding", ConfigUtils.notifications().enable_notifications_background ? modRes.fwd(R.dimen.notification_divider_height) : zero); resparam.res.setReplacement(PACKAGE_SYSTEMUI, "dimen", "notification_padding_dimmed", ConfigUtils.notifications().enable_notifications_background ? modRes.fwd(R.dimen.notification_divider_height) : zero); resparam.res.setReplacement(PACKAGE_SYSTEMUI, "dimen", "notification_material_rounded_rect_radius", zero); resparam.res.setReplacement(PACKAGE_SYSTEMUI, "dimen", "speed_bump_height", zero); resparam.res.setReplacement(PACKAGE_SYSTEMUI, "dimen", "notification_min_height", modRes.fwd(R.dimen.notification_min_height)); resparam.res.setReplacement(PACKAGE_SYSTEMUI, "dimen", "notification_mid_height", modRes.fwd(R.dimen.notification_mid_height)); resparam.res.setReplacement(PACKAGE_SYSTEMUI, "dimen", "notification_max_height", modRes.fwd(R.dimen.notification_max_height)); resparam.res.setReplacement(PACKAGE_SYSTEMUI, "dimen", "min_stack_height", modRes.fwd(R.dimen.min_stack_height)); resparam.res.setReplacement(PACKAGE_SYSTEMUI, "dimen", "keyguard_clock_notifications_margin_min", modRes.fwd(R.dimen.keyguard_clock_notifications_margin_min)); resparam.res.setReplacement(PACKAGE_SYSTEMUI, "dimen", "keyguard_clock_notifications_margin_max", modRes.fwd(R.dimen.keyguard_clock_notifications_margin_max)); resparam.res.setReplacement(PACKAGE_SYSTEMUI, "integer", "keyguard_max_notification_count", config.notifications.keyguard_max); /*try { resparam.res.setReplacement(PACKAGE_SYSTEMUI, "dimen", "notification_children_divider_height", zero); resparam.res.setReplacement(PACKAGE_SYSTEMUI, "dimen", "notification_children_padding", zero); //resparam.res.setReplacement(PACKAGE_SYSTEMUI, "dimen", "z_distance_between_notifications", zero); } catch (Throwable ignore) { }*/ resparam.res.hookLayout(PACKAGE_SYSTEMUI, "layout", "notification_public_default", notification_public_default); resparam.res.hookLayout(PACKAGE_SYSTEMUI, "layout", "status_bar_no_notifications", status_bar_no_notifications); if (ConfigUtils.notifications().experimental) { 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 containerId = context.getResources().getIdentifier("notification_container_parent", "id", PACKAGE_SYSTEMUI); int stackScrollerId = context.getResources().getIdentifier("notification_stack_scroller", "id", PACKAGE_SYSTEMUI); ViewGroup container = (ViewGroup) view.findViewById(containerId); ViewGroup stackScroller = (ViewGroup) container.findViewById(stackScrollerId); container.removeView(stackScroller); container.addView(stackScroller, 0); } }); } resparam.res.hookLayout(PACKAGE_SYSTEMUI, "layout", "status_bar_notification_row", status_bar_notification_row); resparam.res.hookLayout(PACKAGE_SYSTEMUI, "layout", "status_bar_notification_keyguard_overflow", status_bar_notification_row); try { resparam.res.hookLayout(PACKAGE_SYSTEMUI, "layout", "status_bar_notification_row_media", status_bar_notification_row); } catch (Throwable ignore) { } resparam.res.setReplacement(PACKAGE_SYSTEMUI, "drawable", "notification_material_bg", new XResources.DrawableLoader() { @Override public Drawable newDrawable(XResources xResources, int i) throws Throwable { return getNotificationBackground(xResources); } }); resparam.res.setReplacement(PACKAGE_SYSTEMUI, "drawable", "notification_material_bg_dim", new XResources.DrawableLoader() { @Override public Drawable newDrawable(XResources xResources, int i) throws Throwable { return getNotificationBackgroundDimmed(xResources); } }); } if (config.notifications.dismiss_button) { resparam.res.hookLayout(PACKAGE_SYSTEMUI, "layout", "status_bar_notification_dismiss_all", status_bar_notification_dismiss_all); try { resparam.res.hookLayout(PACKAGE_SYSTEMUI, "layout", "recents_dismiss_button", status_bar_notification_dismiss_all); } catch (Exception ignored) { } } } catch (Throwable t) { XposedHook.logE(TAG, "Error hooking SystemUI resources", t); } } @SuppressWarnings("deprecation") private static RippleDrawable getNotificationBackground(XResources xRes) { mNotificationBgColor = xRes.getColor(xRes.getIdentifier("notification_material_background_color", "color", PACKAGE_SYSTEMUI)); return new RippleDrawable( ColorStateList.valueOf(xRes.getColor(xRes.getIdentifier("notification_ripple_untinted_color", "color", PACKAGE_SYSTEMUI))), getBackgroundRippleContent(mNotificationBgColor), null); } @SuppressWarnings("deprecation") private static RippleDrawable getNotificationBackgroundDimmed(XResources xRes) { mNotificationBgDimmedColor = xRes.getColor(xRes.getIdentifier("notification_material_background_dimmed_color", "color", PACKAGE_SYSTEMUI)); return new RippleDrawable( ColorStateList.valueOf(xRes.getColor(xRes.getIdentifier("notification_ripple_untinted_color", "color", PACKAGE_SYSTEMUI))), getBackgroundRippleContent(mNotificationBgDimmedColor), null); } @SuppressWarnings({"deprecation", "ConstantConditions"}) private static Drawable getBackgroundRippleContent(int color) { return new ColorDrawable(color); } @SuppressWarnings("unused") public static void hook(ClassLoader classLoader) { try { if (ConfigUtils.notifications().change_style) { Class classNotificationBuilder = Notification.Builder.class; Class classNotificationStyle = Notification.Style.class; Class classNotificationMediaStyle = Notification.MediaStyle.class; Class classRemoteViews = RemoteViews.class; if (ConfigUtils.M) { XposedHelpers.findAndHookMethod(classNotificationBuilder, "processSmallIconAsLarge", Icon.class, classRemoteViews, processSmallIconAsLargeHook); } else { XposedHelpers.findAndHookMethod(classNotificationBuilder, "processSmallIconAsLarge", int.class, classRemoteViews, processSmallIconAsLargeHook); } XposedHelpers.findAndHookMethod(classNotificationBuilder, "applyLargeIconBackground", classRemoteViews, XC_MethodReplacement.DO_NOTHING); XposedHelpers.findAndHookMethod(classNotificationBuilder, "applyStandardTemplate", int.class, boolean.class, applyStandardTemplateHook); XposedHelpers.findAndHookMethod(classNotificationBuilder, "applyStandardTemplateWithActions", int.class, applyStandardTemplateWithActionsHook); XposedHelpers.findAndHookMethod(classNotificationBuilder, "resetStandardTemplate", RemoteViews.class, resetStandardTemplateHook); XposedHelpers.findAndHookMethod(classNotificationBuilder, "generateActionButton", Notification.Action.class, generateActionButtonHook); XposedHelpers.findAndHookMethod(classNotificationBuilder, "resolveColor", resolveColorHook); XposedHelpers.findAndHookMethod(classNotificationBuilder, "calculateTopPadding", Context.class, boolean.class, float.class, calculateTopPaddingHook); XposedHelpers.findAndHookMethod(classNotificationBuilder, "build", buildHook); XposedHelpers.findAndHookMethod(NotificationCompat.Builder.class, "build", buildHook); XposedHelpers.findAndHookMethod(classNotificationStyle, "getStandardView", int.class, getStandardViewHook); XposedHelpers.findAndHookMethod(classNotificationMediaStyle, "hideRightIcon", RemoteViews.class, XC_MethodReplacement.DO_NOTHING); XposedHelpers.findAndHookMethod(classNotificationMediaStyle, "styleText", RemoteViews.class, XC_MethodReplacement.DO_NOTHING); XposedHelpers.findAndHookMethod(classNotificationMediaStyle, "generateMediaActionButton", Notification.Action.class, generateMediaActionButtonHook); XposedHelpers.findAndHookMethod(classNotificationMediaStyle, "makeMediaContentView", makeMediaContentViewHook); } } catch (Throwable t) { XposedHook.logE(TAG, "Error hooking app", t); } } public static void hookSystemUI(ClassLoader classLoader) { try { ConfigUtils config = ConfigUtils.getInstance(); mSensitiveFilter.hook(classLoader); if (config.notifications.change_style) { StatusBarHooks.create(classLoader); if (ConfigUtils.M) // For now mStackScrollLayoutHooks = new NotificationStackScrollLayoutHooks(classLoader); Class classBaseStatusBar = XposedHelpers.findClass("com.android.systemui.statusbar.BaseStatusBar", classLoader); Class classEntry = XposedHelpers.findClass("com.android.systemui.statusbar.NotificationData.Entry", classLoader); Class classStackScrollAlgorithm = XposedHelpers.findClass("com.android.systemui.statusbar.stack.StackScrollAlgorithm", classLoader); Class classNotificationGuts = XposedHelpers.findClass("com.android.systemui.statusbar.NotificationGuts", classLoader); final Class<?> classExpandableNotificationRow = XposedHelpers.findClass("com.android.systemui.statusbar.ExpandableNotificationRow", classLoader); final Class<?> classMediaExpandableNotificationRow = getClassMediaExpandableNotificationRow(classLoader); Class classPhoneStatusBar = XposedHelpers.findClass("com.android.systemui.statusbar.phone.PhoneStatusBar", classLoader); if (RemoteInputHelper.DIRECT_REPLY_ENABLED) { Class classStatusBarWindowManager = XposedHelpers.findClass(PACKAGE_SYSTEMUI + ".statusbar.phone.StatusBarWindowManager", classLoader); Class classStatusBarWindowManagerState = XposedHelpers.findClass(PACKAGE_SYSTEMUI + ".statusbar.phone.StatusBarWindowManager.State", classLoader); XposedHelpers.findAndHookMethod(classPhoneStatusBar, "addStatusBarWindow", new XC_MethodHook() { @Override protected void afterHookedMethod(MethodHookParam param) throws Throwable { statusBarWindowManager = XposedHelpers.getObjectField(param.thisObject, "mStatusBarWindowManager"); } }); XposedHelpers.findAndHookMethod(classStatusBarWindowManager, "applyFocusableFlag", classStatusBarWindowManagerState, new XC_MethodHook() { @Override protected void beforeHookedMethod(MethodHookParam param) throws Throwable { WindowManager.LayoutParams windowParams = (WindowManager.LayoutParams) XposedHelpers.getObjectField(param.thisObject, ConfigUtils.L1 ? "mLpChanged" : "mLp"); if (remoteInputActive) { windowParams.flags &= ~WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE; windowParams.flags &= ~WindowManager.LayoutParams.FLAG_ALT_FOCUSABLE_IM; param.setResult(null); } windowParams.softInputMode = WindowManager.LayoutParams.SOFT_INPUT_ADJUST_RESIZE; } }); } XposedHelpers.findAndHookMethod(classBaseStatusBar, "inflateViews", classEntry, ViewGroup.class, inflateViewsHook); XposedHelpers.findAndHookMethod(classStackScrollAlgorithm, "initConstants", Context.class, initConstantsHook); XposedHelpers.findAndHookMethod(classNotificationGuts, "onFinishInflate", new XC_MethodHook() { @Override protected void afterHookedMethod(MethodHookParam param) throws Throwable { Drawable bg = (Drawable) XposedHelpers.getObjectField(param.thisObject, "mBackground"); if (bg instanceof ShapeDrawable) { ((ShapeDrawable) bg).getPaint().setPathEffect(null); } else if (bg instanceof GradientDrawable) { ((GradientDrawable) bg).setCornerRadius(0); } } }); XposedHelpers.findAndHookMethod(classExpandableNotificationRow, "setIconAnimationRunningForChild", boolean.class, View.class, new XC_MethodReplacement() { @Override protected Object replaceHookedMethod(MethodHookParam param) throws Throwable { if (classMediaExpandableNotificationRow != null && classMediaExpandableNotificationRow.isAssignableFrom(param.thisObject.getClass())) return null; boolean running = (boolean) param.args[0]; View child = (View) param.args[1]; if (child != null) { ImageView icon = (ImageView) child.findViewById(R.id.icon); if (icon == null) icon = (ImageView) child.findViewById(com.android.internal.R.id.icon); if (icon != null) setIconRunning(param.thisObject, icon, running); ImageView rightIcon = (ImageView) child.findViewById( com.android.internal.R.id.right_icon); if (rightIcon != null) setIconRunning(param.thisObject, rightIcon, running); } return null; } private void setIconRunning(Object row, ImageView icon, boolean running) { XposedHelpers.callMethod(row, "setIconRunning", icon, running); } }); if (ConfigUtils.M && !ConfigUtils.notifications().enable_notifications_background) { XposedHelpers.findAndHookMethod(classExpandableNotificationRow, "setHeadsUp", boolean.class, new XC_MethodHook() { @Override protected void afterHookedMethod(MethodHookParam param) throws Throwable { boolean isHeadsUp = (boolean) param.args[0]; FrameLayout row = (FrameLayout) param.thisObject; row.findViewById(R.id.notification_divider).setAlpha(isHeadsUp ? 0 : 1); } }); } XposedHelpers.findAndHookMethod(classPhoneStatusBar, "start", new XC_MethodHook() { @Override protected void beforeHookedMethod(MethodHookParam param) throws Throwable { mPhoneStatusBar = param.thisObject; } }); XposedHelpers.findAndHookMethod(classPhoneStatusBar, "makeStatusBarView", new XC_MethodHook() { @Override protected void afterHookedMethod(MethodHookParam param) throws Throwable { Object mNavigationBarView = XposedHelpers.getObjectField(NotificationHooks.mPhoneStatusBar, "mNavigationBarView"); if (mNavigationBarView == null) { QSCustomizer qsCustomizer = NotificationPanelHooks.getQsCustomizer(); if (qsCustomizer != null) qsCustomizer.setHasNavBar(false); } } }); XposedHelpers.findAndHookMethod(classPhoneStatusBar, "onBackPressed", new XC_MethodHook() { @Override protected void beforeHookedMethod(MethodHookParam param) throws Throwable { QSCustomizer qsCustomizer = NotificationPanelHooks.getQsCustomizer(); if (qsCustomizer != null && qsCustomizer.onBackPressed()) { param.setResult(true); } } }); XposedHelpers.findAndHookMethod(classPhoneStatusBar, "animateCollapsePanels", int.class, new XC_MethodHook() { @Override protected void beforeHookedMethod(MethodHookParam param) throws Throwable { QSCustomizer qsCustomizer = NotificationPanelHooks.getQsCustomizer(); if (qsCustomizer != null && qsCustomizer.isCustomizing()) { qsCustomizer.hide(true); } } }); if (!ConfigUtils.notifications().enable_notifications_background) { XposedHelpers.findAndHookMethod(classPhoneStatusBar, "updateNotificationShade", new XC_MethodHook() { @Override protected void afterHookedMethod(MethodHookParam param) throws Throwable { ViewGroup stack = (ViewGroup) XposedHelpers.getObjectField(param.thisObject, "mStackScroller"); int childCount = stack.getChildCount(); boolean firstChild = true; for (int i = 0; i < childCount; i++) { View child = stack.getChildAt(i); if (!classExpandableNotificationRow.isAssignableFrom(child.getClass())) { continue; } child.findViewById(R.id.notification_divider).setVisibility(firstChild ? View.INVISIBLE : View.VISIBLE); firstChild = false; } } }); } XposedHelpers.findAndHookMethod(classExpandableNotificationRow, "setExpandable", boolean.class, new XC_MethodHook() { @Override protected void afterHookedMethod(MethodHookParam param) throws Throwable { Object row = param.thisObject; boolean expandable = (boolean) param.args[0]; Object listener = XposedHelpers.getAdditionalInstanceField(row, KEY_EXPAND_CLICK_LISTENER); View.OnClickListener onClickListener = null; if (listener != null && listener instanceof View.OnClickListener) { onClickListener = (View.OnClickListener) listener; } updateExpandButtons(XposedHelpers.getObjectField(row, "mPublicLayout"), expandable, onClickListener); updateExpandButtons(XposedHelpers.getObjectField(row, "mPrivateLayout"), expandable, onClickListener); } }); XC_MethodHook expandedHook = new XC_MethodHook() { @Override protected void afterHookedMethod(MethodHookParam param) throws Throwable { Object row = param.thisObject; boolean userExpanded = (boolean) param.args[0]; updateChildrenExpanded(XposedHelpers.getObjectField(row, "mPublicLayout"), userExpanded); updateChildrenExpanded(XposedHelpers.getObjectField(row, "mPrivateLayout"), userExpanded); } }; XposedHelpers.findAndHookMethod(classExpandableNotificationRow, "setUserExpanded", boolean.class, expandedHook); XposedHelpers.findAndHookMethod(classExpandableNotificationRow, "setSystemExpanded", boolean.class, expandedHook); XposedHelpers.findAndHookMethod(classExpandableNotificationRow, "onFinishInflate", new XC_MethodHook() { @Override protected void afterHookedMethod(MethodHookParam param) throws Throwable { final Object row = param.thisObject; XposedHelpers.setAdditionalInstanceField(param.thisObject, KEY_EXPAND_CLICK_LISTENER, new SafeOnClickListener(TAG, "Error in notification expand icon click") { @Override public void onClickSafe(View v) { boolean nowExpanded = !(boolean) XposedHelpers.callMethod(row, "isExpanded"); XposedHelpers.callMethod(row, "setUserExpanded", nowExpanded); try { XposedHelpers.callMethod(row, "notifyHeightChanged", true); } catch (Throwable t) { XposedHelpers.callMethod(row, "notifyHeightChanged"); } } }); } }); if (ConfigUtils.M) { Class classVolumeDialog = XposedHelpers.findClass("com.android.systemui.volume.VolumeDialog", classLoader); XposedHelpers.findAndHookMethod(classVolumeDialog, "updateWindowWidthH", updateWindowWidthHHook); } else { Class classVolumePanel = XposedHelpers.findClass("com.android.systemui.volume.VolumePanel", classLoader); XposedHelpers.findAndHookMethod(classVolumePanel, "updateWidth", updateWidthHook); } XposedBridge.hookAllMethods(classBaseStatusBar, "applyColorsAndBackgrounds", new XC_MethodHook() { @Override protected void beforeHookedMethod(MethodHookParam param) throws Throwable { try { Object entry = param.args[1]; View contentView = ConfigUtils.M ? (View) XposedHelpers.callMethod(entry, "getContentView") : (View) XposedHelpers.getObjectField(entry, "expanded"); if (contentView.getId() != contentView.getContext().getResources().getIdentifier("status_bar_latest_event_content", "id", PACKAGE_ANDROID)) { // Using custom RemoteViews int targetSdk = XposedHelpers.getIntField(entry, "targetSdk"); if (targetSdk >= Build.VERSION_CODES.GINGERBREAD && targetSdk < Build.VERSION_CODES.LOLLIPOP) { XposedHelpers.callMethod(XposedHelpers.getObjectField(entry, "row"), "setShowingLegacyBackground", true); XposedHelpers.setBooleanField(entry, "legacy", true); } } ImageView icon = (ImageView) XposedHelpers.getObjectField(entry, "icon"); if (icon != null) { int targetSdk = XposedHelpers.getIntField(entry, "targetSdk"); if (Build.VERSION.SDK_INT > 22) { icon.setTag(icon.getResources().getIdentifier("icon_is_pre_L", "id", PACKAGE_SYSTEMUI), targetSdk < Build.VERSION_CODES.LOLLIPOP); } else { if (targetSdk >= Build.VERSION_CODES.LOLLIPOP) { icon.setColorFilter(icon.getResources().getColor(android.R.color.white)); } else { icon.setColorFilter(null); } } } param.setResult(null); } catch (Throwable t) { XposedHook.logE(TAG, "Error in applyColorsAndBackgrounds", t); } } }); } if (config.notifications.dismiss_button) { Class classDismissViewButton; try { classDismissViewButton = XposedHelpers.findClass("com.android.systemui.statusbar.DismissViewButton", classLoader); } catch (Throwable t) { classDismissViewButton = XposedHelpers.findClass("com.android.systemui.statusbar.DismissViewImageButton", classLoader); } XposedHelpers.findAndHookConstructor(classDismissViewButton, Context.class, AttributeSet.class, int.class, int.class, dismissViewButtonConstructorHook); XposedHelpers.findAndHookMethod("com.android.systemui.statusbar.DismissView", classLoader, "setOnButtonClickListener", View.OnClickListener.class, new XC_MethodReplacement() { @Override protected Object replaceHookedMethod(MethodHookParam param) throws Throwable { ((View) XposedHelpers.callMethod(param.thisObject, "findContentView")).setOnClickListener((View.OnClickListener) param.args[0]); return null; } }); } } catch (Throwable t) { XposedHook.logE(TAG, "Error hooking SystemUI", t); } } private static void updateChildrenExpanded(Object notificationContentView, boolean expanded) { View mExpandedChild = (View) XposedHelpers.getObjectField(notificationContentView, "mExpandedChild"); View mContractedChild = (View) XposedHelpers.getObjectField(notificationContentView, "mContractedChild"); View mHeadsUpChild = ConfigUtils.M ? (View) XposedHelpers.getObjectField(notificationContentView, "mHeadsUpChild") : null; if (mExpandedChild != null) { setExpanded(mExpandedChild, expanded); } if (mContractedChild != null) { setExpanded(mContractedChild, expanded); } if (mHeadsUpChild != null) { setExpanded(mHeadsUpChild, expanded); } } private static void setExpanded(View child, boolean expanded) { NotificationHeaderView header = (NotificationHeaderView) child.findViewById(R.id.notification_header); if (header != null) { header.setExpanded(expanded); } } private static void updateExpandButtons(Object notificationContentView, boolean expandable, View.OnClickListener onClickListener) { boolean mIsHeadsUp = false; View mExpandedChild = (View) XposedHelpers.getObjectField(notificationContentView, "mExpandedChild"); View mContractedChild = (View) XposedHelpers.getObjectField(notificationContentView, "mContractedChild"); View mHeadsUpChild = null; if (ConfigUtils.M) { mIsHeadsUp = XposedHelpers.getBooleanField(notificationContentView, "mIsHeadsUp"); mHeadsUpChild = (View) XposedHelpers.getObjectField(notificationContentView, "mHeadsUpChild"); } // if the expanded child has the same height as the collapsed one we hide it. if (mExpandedChild != null && mExpandedChild.getHeight() != 0) { if ((!mIsHeadsUp || mHeadsUpChild == null)) { if (mExpandedChild.getHeight() == mContractedChild.getHeight()) { expandable = false; } } else if (mExpandedChild.getHeight() == mHeadsUpChild.getHeight()) { expandable = false; } } if (mExpandedChild != null) { updateExpandability(mExpandedChild, expandable, onClickListener); } if (mContractedChild != null) { updateExpandability(mContractedChild, expandable, onClickListener); } if (mHeadsUpChild != null) { updateExpandability(mHeadsUpChild, expandable, onClickListener); } } private static void updateExpandability(View target, boolean expandable, View.OnClickListener onClickListener) { ImageView expandButton = (ImageView) target.findViewById(R.id.expand_button); if (expandButton != null) { expandButton.setVisibility(expandable ? View.VISIBLE : View.GONE); NotificationHeaderView header = (NotificationHeaderView) target.findViewById(R.id.notification_header); header.setOnClickListener(expandable ? onClickListener : null); } } private static Class<?> getClassMediaExpandableNotificationRow(ClassLoader classLoader) { try { return XposedHelpers.findClass("com.android.systemui.statusbar.MediaExpandableNotificationRow", classLoader); } catch (Throwable t) { XposedHook.logD(TAG, "Class MediaExpandableNotificationRow not found. Skipping media row check."); } return null; } private static boolean haveRemoteInput(@NonNull Notification.Action[] actions) { for (Notification.Action a : actions) { if (a.getRemoteInputs() != null) { for (RemoteInput ri : a.getRemoteInputs()) { if (ri.getAllowFreeFormInput()) { return true; } } } } return false; } private static boolean hasValidRemoteInput(Notification.Action action) { if ((TextUtils.isEmpty(action.title)) || (action.actionIntent == null)) { return false; } RemoteInput[] remoteInputs = action.getRemoteInputs(); if (remoteInputs == null || remoteInputs.length == 0) { return false; } for (RemoteInput input : remoteInputs) { CharSequence[] choices = input.getChoices(); if (input.getAllowFreeFormInput() || (choices != null && choices.length != 0)) { return true; } } return false; } public static void hookResAndroid(XC_InitPackageResources.InitPackageResourcesParam resparam) { try { if (ConfigUtils.notifications().change_style) { resparam.res.hookLayout(PACKAGE_ANDROID, "layout", "notification_material_action", notification_material_action); //resparam.res.hookLayout(PACKAGE_ANDROID, "layout", "notification_template_icon_group", notification_template_icon_group); resparam.res.hookLayout(PACKAGE_ANDROID, "layout", "notification_template_material_base", notification_template_material_base); resparam.res.hookLayout(PACKAGE_ANDROID, "layout", "notification_template_material_media", notification_template_material_media); resparam.res.hookLayout(PACKAGE_ANDROID, "layout", "notification_template_material_big_base", notification_template_material_base); resparam.res.hookLayout(PACKAGE_ANDROID, "layout", "notification_template_material_big_base", notification_template_material_big_base); // Extra treatment resparam.res.hookLayout(PACKAGE_ANDROID, "layout", "notification_template_material_big_media", notification_template_material_big_media); resparam.res.hookLayout(PACKAGE_ANDROID, "layout", "notification_template_material_big_picture", notification_template_material_big_picture); resparam.res.hookLayout(PACKAGE_ANDROID, "layout", "notification_template_material_big_text", notification_template_material_base); resparam.res.hookLayout(PACKAGE_ANDROID, "layout", "notification_template_material_inbox", notification_template_material_base); resparam.res.hookLayout(PACKAGE_ANDROID, "layout", "notification_material_media_action", new XC_LayoutInflated() { @Override public void handleLayoutInflated(LayoutInflatedParam liparam) throws Throwable { ImageButton action = (ImageButton) liparam.view; Context context = action.getContext(); ResourceUtils res = ResourceUtils.getInstance(context); int width_height = res.getDimensionPixelSize(R.dimen.notification_media_action_width); RelativeLayout.LayoutParams lParams = new RelativeLayout.LayoutParams(width_height, width_height); lParams.setMarginEnd(res.getDimensionPixelSize(R.dimen.notification_media_action_margin)); int padding = ResourceUtils.getInstance(context).getDimensionPixelSize(R.dimen.notification_media_action_padding); action.setPadding(0, padding, 0, padding); action.setBackground(res.getDrawable(R.drawable.notification_material_media_action_background)); } }); resparam.res.hookLayout(PACKAGE_ANDROID, "layout", "notification_material_action_list", new XC_LayoutInflated() { @Override public void handleLayoutInflated(LayoutInflatedParam liparam) throws Throwable { LinearLayout container = (LinearLayout) liparam.view; Context context = container.getContext(); ResourceUtils res = ResourceUtils.getInstance(context); int actionsId = container.getId(); container.setId(R.id.actions_container); ViewUtils.setMarginBottom(container, 0); NotificationActionListLayout notificationActionListLayout = new NotificationActionListLayout(context, null); notificationActionListLayout.setId(actionsId); notificationActionListLayout.setLayoutParams(new LinearLayout.LayoutParams(MATCH_PARENT, res.getDimensionPixelSize(R.dimen.notification_action_list_height))); container.addView(notificationActionListLayout); } }); resparam.res.hookLayout(PACKAGE_ANDROID, "layout", "notification_template_part_line1", new XC_LayoutInflated() { @Override public void handleLayoutInflated(LayoutInflatedParam liparam) throws Throwable { LinearLayout layout = (LinearLayout) liparam.view; while (layout.getChildCount() > 1) { layout.removeViewAt(1); } Context context = layout.getContext(); TextView title = (TextView) layout.findViewById(context.getResources().getIdentifier("title", "id", PACKAGE_ANDROID)); LinearLayout.LayoutParams titleLp = (LinearLayout.LayoutParams) title.getLayoutParams(); titleLp.width = WRAP_CONTENT; titleLp.weight = 0; title.setTextSize(TypedValue.COMPLEX_UNIT_PX, ResourceUtils.getInstance().getDimensionPixelSize(R.dimen.notification_title_text_size)); layout.removeView(title); LinearLayout container = new RemoteMarginLinearLayout(context); container.setOrientation(LinearLayout.HORIZONTAL); container.setId(R.id.line1_container); TextView textLine1 = new TextView(context); textLine1.setId(R.id.text_line_1); textLine1.setTextAppearance(context, android.R.style.TextAppearance_Material_Notification); textLine1.setGravity(Gravity.START | Gravity.BOTTOM); textLine1.setSingleLine(true); textLine1.setEllipsize(TextUtils.TruncateAt.END); textLine1.setHorizontalFadingEdgeEnabled(true); LinearLayout.LayoutParams textLine1Lp = new LinearLayout.LayoutParams(MATCH_PARENT, WRAP_CONTENT); textLine1Lp.setMarginStart(ResourceUtils.getInstance(context).getDimensionPixelSize(R.dimen.notification_text_line1_margin_start)); textLine1.setLayoutParams(textLine1Lp); container.addView(title); container.addView(textLine1); layout.addView(container); } }); resparam.res.hookLayout(PACKAGE_ANDROID, "layout", "notification_template_part_line2", new XC_LayoutInflated() { @Override public void handleLayoutInflated(LayoutInflatedParam liparam) throws Throwable { Context context = liparam.view.getContext(); ResourceUtils res = ResourceUtils.getInstance(context); int notificationTextMarginEnd = res.getDimensionPixelSize(R.dimen.notification_text_margin_end); ViewStub progress = (ViewStub) liparam.view.findViewById(context.getResources().getIdentifier("progress", "id", PACKAGE_ANDROID)); ViewUtils.setMarginEnd(progress, notificationTextMarginEnd); } }); resparam.res.hookLayout(PACKAGE_ANDROID, "layout", "notification_template_part_line3", new XC_LayoutInflated() { @Override public void handleLayoutInflated(LayoutInflatedParam liparam) throws Throwable { LinearLayout layout = (LinearLayout) liparam.view; Context context = layout.getContext(); if (layout.getChildAt(1) != null) { layout.removeViewAt(1); } LinearLayout container = new RemoteMarginLinearLayout(context); container.setId(R.id.line3_container); while (layout.getChildCount() > 0) { View view = layout.getChildAt(0); layout.removeView(view); container.addView(view); } layout.addView(container); } }); } } catch (Throwable t) { XposedHook.logE(TAG, "Error hooking framework resources", t); } } private static final XC_LayoutInflated notification_template_material_big_base = new XC_LayoutInflated() { @Override public void handleLayoutInflated(LayoutInflatedParam liparam) throws Throwable { FrameLayout layout = (FrameLayout) liparam.view; Context context = layout.getContext(); ResourceUtils res = ResourceUtils.getInstance(context); int notificationTextMarginEnd = res.getDimensionPixelSize(R.dimen.notification_text_margin_end); TextView bigText = (TextView) layout.findViewById(context.getResources().getIdentifier("big_text", "id", PACKAGE_ANDROID)); if (bigText != null) { ViewUtils.setMarginEnd(bigText, notificationTextMarginEnd); } } }; private static final XC_LayoutInflated status_bar_no_notifications = new XC_LayoutInflated() { @Override public void handleLayoutInflated(LayoutInflatedParam liparam) throws Throwable { FrameLayout layout = (FrameLayout) liparam.view; Context context = layout.getContext(); ResourceUtils res = ResourceUtils.getInstance(context); int height = res.getDimensionPixelSize(R.dimen.notification_min_height); int paddingTop = res.getDimensionPixelSize(R.dimen.no_notifications_padding_top); int textSize = res.getDimensionPixelSize(R.dimen.no_notifications_text_size); TextView textView = (TextView) layout.findViewById(context.getResources().getIdentifier("no_notifications", "id", PACKAGE_SYSTEMUI)); FrameLayout.LayoutParams textViewLp = (FrameLayout.LayoutParams) textView.getLayoutParams(); textViewLp.height = height; int paddingLeft = textView.getPaddingLeft(); int paddingRight = textView.getPaddingRight(); int paddingBottom = textView.getPaddingBottom(); textView.setLayoutParams(textViewLp); textView.setPadding(paddingLeft, paddingTop, paddingRight, paddingBottom); textView.setTextSize(TypedValue.COMPLEX_UNIT_PX, textSize); } }; private static final XC_LayoutInflated status_bar_notification_dismiss_all = new XC_LayoutInflated() { @Override public void handleLayoutInflated(LayoutInflatedParam liparam) throws Throwable { FrameLayout layout = (FrameLayout) liparam.view; Context context = layout.getContext(); ResourceUtils res = ResourceUtils.getInstance(context); int dismissButtonPadding = res.getDimensionPixelSize(R.dimen.notification_dismiss_button_padding); int dismissButtonPaddingTop = res.getDimensionPixelSize(R.dimen.notification_dismiss_button_padding_top); View buttonView = layout.getChildAt(0); if (buttonView instanceof LinearLayout) { ((LinearLayout) buttonView).getChildAt(0).setVisibility(View.GONE); ((LinearLayout) buttonView).getChildAt(1).setVisibility(View.VISIBLE); buttonView = ((LinearLayout) buttonView).getChildAt(1); buttonView.setId(context.getResources().getIdentifier("dismiss_text", "id", PACKAGE_SYSTEMUI)); } if (buttonView instanceof ImageButton) { layout.removeView(buttonView); buttonView = new Button(context); buttonView.setId(context.getResources().getIdentifier("dismiss_text", "id", PACKAGE_SYSTEMUI)); buttonView.setFocusable(true); buttonView.setContentDescription(context.getResources().getString(context.getResources().getIdentifier("accessibility_clear_all", "string", PACKAGE_SYSTEMUI))); layout.addView(buttonView); } TextView button = (TextView) buttonView; // It's a TextView on some ROMs if (button.getParent() instanceof LinearLayout) { // this is probably only for Xperia devices LinearLayout.LayoutParams lp = new LinearLayout.LayoutParams(WRAP_CONTENT, WRAP_CONTENT); lp.gravity = Gravity.END; button.setLayoutParams(lp); LinearLayout parent = (LinearLayout) button.getParent(); parent.setBackground(null); LinearLayout.MarginLayoutParams lp2 = (LinearLayout.MarginLayoutParams) parent.getLayoutParams(); lp2.setMarginEnd(0); parent.setLayoutParams(lp2); } else { FrameLayout.LayoutParams lp = new FrameLayout.LayoutParams(WRAP_CONTENT, WRAP_CONTENT); lp.gravity = Gravity.END; button.setLayoutParams(lp); } button.setTypeface(Typeface.create("sans-serif-medium", Typeface.NORMAL)); button.setTextColor(res.getColor(android.R.color.white)); button.setAllCaps(true); button.setText(context.getString(context.getResources().getIdentifier("clear_all_notifications_text", "string", PACKAGE_SYSTEMUI))); button.setBackground(res.getDrawable(R.drawable.ripple_dismiss_all)); button.setPadding(dismissButtonPadding, dismissButtonPaddingTop, dismissButtonPadding, dismissButtonPadding); } }; private static final XC_LayoutInflated notification_template_material_base = new XC_LayoutInflated() { @Override public void handleLayoutInflated(XC_LayoutInflated.LayoutInflatedParam liparam) throws Throwable { ViewGroup layout = (ViewGroup) liparam.view; Context context = layout.getContext(); ResourceUtils res = ResourceUtils.getInstance(context); layout.removeViewAt(0); layout.addView(NotificationHeaderView.newHeader(context), 0); int notificationContentPadding = res.getDimensionPixelSize(R.dimen.notification_content_margin_start); int notificationContentPaddingTop = res.getDimensionPixelSize(R.dimen.notification_content_margin_top); int actionsMarginTop = res.getDimensionPixelSize(R.dimen.notification_actions_margin_top); LinearLayout notificationMain = (LinearLayout) layout.findViewById(context.getResources().getIdentifier("notification_main_column", "id", "android")); if (notificationMain == null) { // Some ROMs completely removed the ID notificationMain = (LinearLayout) layout.getChildAt(layout.getChildCount() - 1); } FrameLayout.LayoutParams notificationMainLParams = new FrameLayout.LayoutParams(MATCH_PARENT, MATCH_PARENT); notificationMainLParams.setMargins(0, notificationContentPaddingTop, 0, 0); notificationMain.setLayoutParams(notificationMainLParams); ImageView rightIcon = getRightIcon(context); layout.addView(rightIcon); ViewGroup.LayoutParams params = layout.getLayoutParams(); if (params == null) params = new ViewGroup.LayoutParams(MATCH_PARENT, WRAP_CONTENT); params.height = WRAP_CONTENT; layout.setLayoutParams(params); boolean isInboxLayout = liparam.resNames.fullName.contains("notification_template_material_inbox"); boolean isBigTextLayout = liparam.resNames.fullName.contains("notification_template_material_big_text"); // Margins for every child except actions container int actionsId = context.getResources().getIdentifier("actions", "id", PACKAGE_ANDROID); int childCount = notificationMain.getChildCount(); for (int i = 0; i < childCount; i++) { View child = notificationMain.getChildAt(i); int id = child.getId(); if (id == R.id.actions_container) { if (!isInboxLayout) { ViewGroup.MarginLayoutParams childLp = (ViewGroup.MarginLayoutParams) child.getLayoutParams(); childLp.topMargin += actionsMarginTop; child.setLayoutParams(childLp); } if (ConfigUtils.notifications().custom_actions_color) { child.findViewById(actionsId).setBackgroundColor(ConfigUtils.notifications().actions_color); } } else { ViewGroup.MarginLayoutParams childLp = (ViewGroup.MarginLayoutParams) child.getLayoutParams(); childLp.leftMargin += notificationContentPadding; childLp.rightMargin += notificationContentPadding; child.setLayoutParams(childLp); } } if (isInboxLayout || isBigTextLayout) { int notificationTextMarginEnd = res.getDimensionPixelSize(R.dimen.notification_text_inbox_margin_end); if (isInboxLayout) { View inboxText0 = notificationMain.findViewById(context.getResources().getIdentifier("inbox_text0", "id", PACKAGE_ANDROID)); ViewUtils.setMarginEnd(inboxText0, notificationTextMarginEnd); } if (isBigTextLayout) { View bigText = notificationMain.findViewById(context.getResources().getIdentifier("big_text", "id", PACKAGE_ANDROID)); ViewUtils.setMarginEnd(bigText, notificationTextMarginEnd); } // Remove divider notificationMain.removeViewAt(notificationMain.getChildCount() - 2); // Remove bottom line notificationMain.removeViewAt(notificationMain.getChildCount() - 1); } } }; private static final XC_LayoutInflated status_bar_notification_row = new XC_LayoutInflated() { @Override public void handleLayoutInflated(XC_LayoutInflated.LayoutInflatedParam liparam) throws Throwable { FrameLayout row = (FrameLayout) liparam.view; Context context = row.getContext(); ResourceUtils res = ResourceUtils.getInstance(context); if (!ConfigUtils.notifications().enable_notifications_background) { int dividerHeight = res.getDimensionPixelSize(R.dimen.notification_separator_size); FrameLayout.LayoutParams dividerLp = new FrameLayout.LayoutParams(MATCH_PARENT, dividerHeight); dividerLp.gravity = Gravity.TOP; View divider = new View(context); divider.setBackgroundColor(0x1F000000); divider.setId(R.id.notification_divider); divider.setLayoutParams(dividerLp); row.addView(divider); } if (ConfigUtils.M) { FrameLayout.LayoutParams fakeShadowLp = new FrameLayout.LayoutParams(MATCH_PARENT, MATCH_PARENT); FakeShadowView fakeShadow = new FakeShadowView(context); fakeShadow.setId(R.id.fake_shadow); fakeShadow.setLayoutParams(fakeShadowLp); row.addView(fakeShadow); } } }; private static final XC_LayoutInflated notification_material_action = new XC_LayoutInflated() { @Override public void handleLayoutInflated(XC_LayoutInflated.LayoutInflatedParam liparam) throws Throwable { Button button = (Button) liparam.view; ViewGroup.MarginLayoutParams buttonLp = (ViewGroup.MarginLayoutParams) button.getLayoutParams(); buttonLp.height = MATCH_PARENT; buttonLp.width = WRAP_CONTENT; button.setLayoutParams(buttonLp); } }; @NonNull private static ImageView getRightIcon(Context context) { ResourceUtils res = ResourceUtils.getInstance(context); int rightIconSize = res.getDimensionPixelSize(R.dimen.notification_right_icon_size); int rightIconMarginTop = res.getDimensionPixelSize(R.dimen.notification_right_icon_margin_top); int rightIconMarginEnd = res.getDimensionPixelSize(R.dimen.notification_right_icon_margin_end); ImageView rightIcon = new ImageView(context); //noinspection SuspiciousNameCombination FrameLayout.LayoutParams rightIconLp = new FrameLayout.LayoutParams(rightIconSize, rightIconSize); rightIconLp.setMargins(0, rightIconMarginTop, 0, 0); rightIconLp.setMarginEnd(rightIconMarginEnd); rightIconLp.gravity = Gravity.TOP | Gravity.END; rightIcon.setLayoutParams(rightIconLp); rightIcon.setId(context.getResources().getIdentifier("right_icon", "id", "android")); return rightIcon; } @NonNull private static ImageView getLargeRightIcon(Context context) { ResourceUtils res = ResourceUtils.getInstance(context); int rightIconSize = res.getDimensionPixelSize(R.dimen.media_notification_expanded_image_max_size); int rightIconMarginBottom = res.getDimensionPixelSize(R.dimen.notification_right_icon_margin_bottom); int rightIconMarginEnd = res.getDimensionPixelSize(R.dimen.notification_right_icon_margin_end); ImageView rightIcon = new ImageView(context); //noinspection SuspiciousNameCombination FrameLayout.LayoutParams rightIconLp = new FrameLayout.LayoutParams(rightIconSize, rightIconSize); rightIconLp.setMargins(0, 0, 0, rightIconMarginBottom); rightIconLp.setMarginEnd(rightIconMarginEnd); rightIconLp.gravity = Gravity.BOTTOM | Gravity.END; rightIcon.setLayoutParams(rightIconLp); rightIcon.setId(context.getResources().getIdentifier("right_icon", "id", "android")); return rightIcon; } private static final XC_LayoutInflated notification_template_material_big_media = new XC_LayoutInflated() { @Override public void handleLayoutInflated(LayoutInflatedParam liparam) throws Throwable { RelativeLayout oldLayout = (RelativeLayout) liparam.view; Context context = oldLayout.getContext(); ResourceUtils res = ResourceUtils.getInstance(context); View mediaActions = oldLayout.findViewById(context.getResources().getIdentifier("media_actions", "id", PACKAGE_ANDROID)); oldLayout.removeAllViews(); FrameLayout layout = (FrameLayout) LayoutInflater.from(context).inflate(context.getResources().getIdentifier("notification_template_material_base", "layout", PACKAGE_ANDROID), null); layout.setId(R.id.dummy_id); oldLayout.addView(layout); LinearLayout notificationMain = (LinearLayout) layout.findViewById(context.getResources().getIdentifier("notification_main_column", "id", "android")); notificationMain.setMinimumHeight(res.getDimensionPixelSize(R.dimen.notification_min_content_height)); ViewUtils.setMarginEnd(notificationMain.findViewById(context.getResources().getIdentifier("title", "id", PACKAGE_ANDROID)), 0); ViewUtils.setMarginEnd(notificationMain.findViewById(context.getResources().getIdentifier("text", "id", PACKAGE_ANDROID)), 0); LinearLayout contentContainer = new LinearLayout(context); contentContainer.setPadding(0, res.getDimensionPixelSize(R.dimen.notification_content_margin_top), 0, 0); contentContainer.setOrientation(LinearLayout.VERTICAL); LinearLayout.LayoutParams contentContainerLp = new LinearLayout.LayoutParams(MATCH_PARENT, WRAP_CONTENT); contentContainerLp.gravity = Gravity.FILL_VERTICAL; contentContainerLp.weight = 1; contentContainer.setLayoutParams(contentContainerLp); LinearLayout.LayoutParams mediaActionsLp = new LinearLayout.LayoutParams(WRAP_CONTENT, WRAP_CONTENT); mediaActionsLp.gravity = Gravity.TOP; mediaActionsLp.setMargins(0, 0, 0, res.getDimensionPixelSize(R.dimen.media_actions_margin_bottom)); mediaActionsLp.setMarginStart(res.getDimensionPixelSize(R.dimen.big_media_actions_margin_start)); mediaActions.setLayoutParams(mediaActionsLp); layout.removeView(layout.findViewById(context.getResources().getIdentifier("right_icon", "id", "android"))); layout.removeView(notificationMain); ViewUtils.setMarginEnd(layout.findViewById(R.id.notification_header), res.getDimensionPixelSize(R.dimen.notification_content_plus_big_picture_margin_end)); FrameLayout.LayoutParams notificationMainLp = new FrameLayout.LayoutParams(MATCH_PARENT, MATCH_PARENT); notificationMainLp.setMargins(0, 0, 0, res.getDimensionPixelSize(R.dimen.notification_content_margin_bottom)); notificationMainLp.setMarginStart(res.getDimensionPixelSize(R.dimen.notification_content_margin_start)); notificationMainLp.setMarginEnd(res.getDimensionPixelSize(R.dimen.notification_content_plus_picture_margin_end)); notificationMain.setLayoutParams(notificationMainLp); contentContainer.addView(notificationMain); contentContainer.addView(mediaActions); layout.addView(contentContainer); layout.addView(getLargeRightIcon(context)); } }; private static final XC_LayoutInflated notification_template_material_media = new XC_LayoutInflated() { @Override public void handleLayoutInflated(XC_LayoutInflated.LayoutInflatedParam liparam) throws Throwable { LinearLayout oldLayout = (LinearLayout) liparam.view; Context context = oldLayout.getContext(); ResourceUtils res = ResourceUtils.getInstance(context); View mediaActions = oldLayout.findViewById(context.getResources().getIdentifier("media_actions", "id", PACKAGE_ANDROID)); oldLayout.removeAllViews(); FrameLayout layout = (FrameLayout) LayoutInflater.from(context).inflate(context.getResources().getIdentifier("notification_template_material_base", "layout", PACKAGE_ANDROID), null); layout.setId(R.id.dummy_id); oldLayout.addView(layout); LinearLayout notificationMain = (LinearLayout) layout.findViewById(context.getResources().getIdentifier("notification_main_column", "id", "android")); notificationMain.setOrientation(LinearLayout.HORIZONTAL); ViewUtils.setMarginEnd(notificationMain.findViewById(context.getResources().getIdentifier("title", "id", PACKAGE_ANDROID)), 0); ViewUtils.setMarginEnd(notificationMain.findViewById(context.getResources().getIdentifier("text", "id", PACKAGE_ANDROID)), 0); LinearLayout contentContainer = new LinearLayout(context); contentContainer.setOrientation(LinearLayout.VERTICAL); while (notificationMain.getChildCount() > 0) { View view = notificationMain.getChildAt(0); notificationMain.removeViewAt(0); contentContainer.addView(view); } layout.removeView(notificationMain); int notificationMainId = notificationMain.getId(); notificationMain = new RemoteMarginLinearLayout(context); notificationMain.setId(notificationMainId); layout.addView(notificationMain, 1); FrameLayout.LayoutParams notificationMainLp = new FrameLayout.LayoutParams(MATCH_PARENT, MATCH_PARENT); notificationMainLp.setMargins(0, res.getDimensionPixelSize(R.dimen.notification_content_margin_top), 0, 0); notificationMain.setLayoutParams(notificationMainLp); LinearLayout.LayoutParams contentContainerLp = new LinearLayout.LayoutParams(MATCH_PARENT, WRAP_CONTENT); contentContainerLp.gravity = Gravity.FILL_VERTICAL; contentContainerLp.weight = 1; contentContainer.setLayoutParams(contentContainerLp); contentContainer.setMinimumHeight(res.getDimensionPixelSize(R.dimen.notification_min_content_height)); contentContainer.setPadding(0, 0, 0, 0); int mediaMargin = res.getDimensionPixelSize(R.dimen.media_actions_margin_bottom); LinearLayout.LayoutParams mediaActionsLp = new LinearLayout.LayoutParams(WRAP_CONTENT, WRAP_CONTENT); mediaActionsLp.gravity = Gravity.BOTTOM | Gravity.END; mediaActionsLp.setMargins(0, mediaMargin, 0, mediaMargin); mediaActionsLp.setMarginStart(res.getDimensionPixelSize(R.dimen.media_actions_margin_start)); mediaActions.setLayoutParams(mediaActionsLp); notificationMain.addView(contentContainer); notificationMain.addView(mediaActions); } }; private static final XC_LayoutInflated notification_template_material_big_picture = new XC_LayoutInflated() { @Override public void handleLayoutInflated(LayoutInflatedParam liparam) throws Throwable { FrameLayout layout = (FrameLayout) liparam.view; View pic = layout.getChildAt(0); View shadow = layout.getChildAt(1); View base = layout.getChildAt(2); View actions = layout.getChildAt(3); Context context = layout.getContext(); ResourceUtils res = ResourceUtils.getInstance(context); int notificationHeight = res.getDimensionPixelSize(R.dimen.notification_min_height); int notificationActionsPadding = res.getDimensionPixelSize(R.dimen.notification_actions_margin_start); FrameLayout.LayoutParams picLParams = (FrameLayout.LayoutParams) pic.getLayoutParams(); FrameLayout.LayoutParams shadowLParams = (FrameLayout.LayoutParams) shadow.getLayoutParams(); FrameLayout.LayoutParams baseLParams = (FrameLayout.LayoutParams) base.getLayoutParams(); picLParams.setMargins(0, notificationHeight, 0, 0); shadowLParams.setMargins(0, notificationHeight, 0, 0); baseLParams.height = notificationHeight; pic.setLayoutParams(picLParams); shadow.setLayoutParams(shadowLParams); base.setLayoutParams(baseLParams); actions.setPadding(notificationActionsPadding, 0, notificationActionsPadding, 0); } }; @SuppressWarnings("deprecation") private static final XC_LayoutInflated notification_public_default = new XC_LayoutInflated() { @Override public void handleLayoutInflated(XC_LayoutInflated.LayoutInflatedParam liparam) throws Throwable { RelativeLayout layout = (RelativeLayout) liparam.view; Context context = layout.getContext(); ResourceUtils res = ResourceUtils.getInstance(context); int notificationContentPadding = res.getDimensionPixelSize(R.dimen.notification_content_margin_start); int notificationContentMarginTop = res.getDimensionPixelSize(R.dimen.notification_content_margin_top); int notificationHeaderMarginTop = res.getDimensionPixelSize(R.dimen.notification_fake_header_margin_top); int iconSize = res.getDimensionPixelSize(R.dimen.notification_icon_size); int iconMarginEnd = res.getDimensionPixelSize(R.dimen.notification_icon_margin_end); int appNameMarginStart = res.getDimensionPixelSize(R.dimen.notification_app_name_margin_start); int appNameMarginEnd = res.getDimensionPixelSize(R.dimen.notification_app_name_margin_end); int dividerMarginTop = res.getDimensionPixelSize(R.dimen.notification_divider_margin_top); int timeMarginStart = res.getDimensionPixelSize(R.dimen.notification_time_margin_start); int iconId = context.getResources().getIdentifier("icon", "id", PACKAGE_SYSTEMUI); int timeId = context.getResources().getIdentifier("time", "id", PACKAGE_SYSTEMUI); int titleId = context.getResources().getIdentifier("title", "id", PACKAGE_SYSTEMUI); ImageView icon = (ImageView) layout.findViewById(iconId); DateTimeView time = (DateTimeView) layout.findViewById(timeId); TextView title = (TextView) layout.findViewById(titleId); TextView textView = new TextView(context); TextView divider = new TextView(context); RelativeLayout.LayoutParams iconLParams = new RelativeLayout.LayoutParams(iconSize, iconSize); iconLParams.setMargins(notificationContentPadding, notificationHeaderMarginTop, iconMarginEnd, 0); RelativeLayout.LayoutParams timeLParams = new RelativeLayout.LayoutParams(WRAP_CONTENT, WRAP_CONTENT); timeLParams.setMargins(timeMarginStart, 0, 0, 0); timeLParams.addRule(RelativeLayout.RIGHT_OF, R.id.public_app_name_text); timeLParams.addRule(RelativeLayout.ALIGN_TOP, iconId); RelativeLayout.LayoutParams titleLParams = new RelativeLayout.LayoutParams(WRAP_CONTENT, WRAP_CONTENT); titleLParams.setMargins(notificationContentPadding, notificationContentMarginTop, 0, 0); //titleLParams.addRule(RelativeLayout.BELOW, iconId); RelativeLayout.LayoutParams textViewLParams = new RelativeLayout.LayoutParams(WRAP_CONTENT, WRAP_CONTENT); textViewLParams.setMarginStart(appNameMarginStart); textViewLParams.setMarginEnd(appNameMarginEnd); textViewLParams.addRule(RelativeLayout.ALIGN_TOP, iconId); textViewLParams.addRule(RelativeLayout.RIGHT_OF, iconId); RelativeLayout.LayoutParams dividerLParams = new RelativeLayout.LayoutParams(WRAP_CONTENT, WRAP_CONTENT); dividerLParams.setMargins(0, dividerMarginTop, 0, 0); dividerLParams.addRule(RelativeLayout.RIGHT_OF, R.id.public_app_name_text); dividerLParams.addRule(RelativeLayout.ALIGN_TOP, iconId); time.setGravity(View.TEXT_ALIGNMENT_CENTER); textView.setId(R.id.public_app_name_text); textView.setTextAppearance(context, context.getResources().getIdentifier("TextAppearance.Material.Notification.Info", "style", "android")); divider.setId(R.id.public_time_divider); divider.setLayoutParams(dividerLParams); divider.setText(res.getString(R.string.notification_header_divider_symbol)); divider.setTextAppearance(context, context.getResources().getIdentifier("TextAppearance.Material.Notification.Info", "style", "android")); divider.setVisibility(View.GONE); icon.setLayoutParams(iconLParams); time.setLayoutParams(timeLParams); title.setLayoutParams(titleLParams); textView.setLayoutParams(textViewLParams); layout.addView(textView); layout.addView(divider); } }; }