55
66import androidx .annotation .NonNull ;
77
8+ import java .lang .reflect .Method ;
9+ import java .util .ArrayList ;
10+
811import cc .ioctl .tmoe .R ;
912import cc .ioctl .tmoe .fragment .SettingsFragment ;
1013import cc .ioctl .tmoe .lifecycle .Parasitics ;
1114import cc .ioctl .tmoe .rtti .ProxyFragmentRttiHandler ;
1215import cc .ioctl .tmoe .ui .LocaleController ;
1316import cc .ioctl .tmoe .util .HostInfo ;
17+ import cc .ioctl .tmoe .util .Initiator ;
1418import cc .ioctl .tmoe .util .Reflex ;
1519import cc .ioctl .tmoe .util .Utils ;
20+ import de .robv .android .xposed .XC_MethodHook ;
21+ import de .robv .android .xposed .XposedBridge ;
1622
1723public class SettingEntryHook implements Initializable , ProfileActivityRowHook .Callback {
1824 public static final SettingEntryHook INSTANCE = new SettingEntryHook ();
@@ -21,6 +27,12 @@ private SettingEntryHook() {
2127 }
2228
2329 private static final String TMOE_SETTINGS_ROW = "TMOE_SETTINGS_ROW" ;
30+ private static final int TMOE_SETTINGS_ITEM_ID = 0x7F0E0001 ;
31+ private static final int SETTINGS_ACTIVITY_LANGUAGE_ITEM_ID = 10 ;
32+ private static final int SETTINGS_ACTIVITY_ICON_COLOR_TOP = 0xFFB07AF5 ;
33+ private static final int SETTINGS_ACTIVITY_ICON_COLOR_BOTTOM = 0xFF8C57E8 ;
34+ private static Method sSettingsCellFactoryMethod = null ;
35+ private static boolean sSettingsActivityHooked = false ;
2436
2537 private boolean mInitialized = false ;
2638
@@ -30,6 +42,7 @@ public boolean initialize() {
3042 return true ;
3143 }
3244 ProfileActivityRowHook .addCallback (this );
45+ hookSettingsActivityEntry ();
3346 mInitialized = true ;
3447 return true ;
3548 }
@@ -99,4 +112,126 @@ public void onInsertRow(@NonNull ProfileActivityRowHook.RowManipulator manipulat
99112 }
100113 manipulator .insertRowAtPosition (TMOE_SETTINGS_ROW , targetRow );
101114 }
115+
116+ private static void hookSettingsActivityEntry () {
117+ if (sSettingsActivityHooked ) {
118+ return ;
119+ }
120+ try {
121+ Class <?> kSettingsActivity = Initiator .load ("org.telegram.ui.SettingsActivity" );
122+ if (kSettingsActivity == null ) {
123+ // old versions do not have SettingsActivity, keep legacy ProfileActivity hook only
124+ return ;
125+ }
126+ Class <?> kSettingCellFactory = Initiator .load ("org.telegram.ui.SettingsActivity$SettingCell$Factory" );
127+ if (kSettingCellFactory == null ) {
128+ Utils .loge ("unable to find SettingsActivity$SettingCell$Factory" );
129+ return ;
130+ }
131+ sSettingsCellFactoryMethod = kSettingCellFactory .getDeclaredMethod (
132+ "of" ,
133+ int .class , int .class , int .class , int .class , CharSequence .class , CharSequence .class , CharSequence .class
134+ );
135+ sSettingsCellFactoryMethod .setAccessible (true );
136+
137+ Method fillItems = null ;
138+ Method onClick = null ;
139+ for (Method m : kSettingsActivity .getDeclaredMethods ()) {
140+ if (fillItems == null && "fillItems" .equals (m .getName ())
141+ && m .getParameterTypes ().length == 2
142+ && ArrayList .class .isAssignableFrom (m .getParameterTypes ()[0 ])) {
143+ fillItems = m ;
144+ } else if (onClick == null && "onClick" .equals (m .getName ())
145+ && m .getParameterTypes ().length == 5 ) {
146+ onClick = m ;
147+ }
148+ }
149+ if (fillItems == null || onClick == null ) {
150+ Utils .loge ("unable to find SettingsActivity#fillItems or #onClick" );
151+ return ;
152+ }
153+
154+ XposedBridge .hookMethod (fillItems , new XC_MethodHook () {
155+ @ Override
156+ protected void afterHookedMethod (MethodHookParam param ) {
157+ if (!(param .args [0 ] instanceof ArrayList )) {
158+ return ;
159+ }
160+ @ SuppressWarnings ("unchecked" )
161+ ArrayList <Object > items = (ArrayList <Object >) param .args [0 ];
162+ if (items .isEmpty ()) {
163+ return ;
164+ }
165+ int insertAt = findSettingsActivityLanguageItemIndex (items );
166+ if (insertAt < 0 || containsSettingsActivityItem (items , TMOE_SETTINGS_ITEM_ID )) {
167+ return ;
168+ }
169+ Object item = createSettingsActivityItem ();
170+ if (item != null ) {
171+ items .add (insertAt , item );
172+ }
173+ }
174+ });
175+
176+ XposedBridge .hookMethod (onClick , new XC_MethodHook () {
177+ @ Override
178+ protected void beforeHookedMethod (MethodHookParam param ) {
179+ Object item = param .args [0 ];
180+ if (getSettingsActivityItemId (item ) == TMOE_SETTINGS_ITEM_ID ) {
181+ presentTMoeSettingsFragment (param .thisObject );
182+ param .setResult (null );
183+ }
184+ }
185+ });
186+ sSettingsActivityHooked = true ;
187+ } catch (Throwable e ) {
188+ Utils .loge (e );
189+ }
190+ }
191+
192+ private static Object createSettingsActivityItem () {
193+ if (sSettingsCellFactoryMethod == null ) {
194+ return null ;
195+ }
196+ try {
197+ Parasitics .injectModuleResources (HostInfo .getApplication ().getResources ());
198+ String text = LocaleController .getString ("TMoeSettings" , R .string .TMoeSettings );
199+ return sSettingsCellFactoryMethod .invoke (
200+ null ,
201+ TMOE_SETTINGS_ITEM_ID ,
202+ SETTINGS_ACTIVITY_ICON_COLOR_TOP ,
203+ SETTINGS_ACTIVITY_ICON_COLOR_BOTTOM ,
204+ R .drawable .ic_setting_hex_outline_24 ,
205+ text ,
206+ null ,
207+ null
208+ );
209+ } catch (Throwable e ) {
210+ Utils .loge (e );
211+ return null ;
212+ }
213+ }
214+
215+ private static int findSettingsActivityLanguageItemIndex (@ NonNull ArrayList <?> items ) {
216+ for (int i = 0 ; i < items .size (); i ++) {
217+ if (getSettingsActivityItemId (items .get (i )) == SETTINGS_ACTIVITY_LANGUAGE_ITEM_ID ) {
218+ return i ;
219+ }
220+ }
221+ return -1 ;
222+ }
223+
224+ private static boolean containsSettingsActivityItem (@ NonNull ArrayList <?> items , int itemId ) {
225+ for (Object item : items ) {
226+ if (getSettingsActivityItemId (item ) == itemId ) {
227+ return true ;
228+ }
229+ }
230+ return false ;
231+ }
232+
233+ private static int getSettingsActivityItemId (Object item ) {
234+ Object id = item == null ? null : Reflex .getInstanceObjectOrNull (item , "id" );
235+ return id instanceof Number ? ((Number ) id ).intValue () : Integer .MIN_VALUE ;
236+ }
102237}
0 commit comments