x1ongzhu 1 год назад
Родитель
Сommit
d9158959f5

+ 6 - 4
app/build.gradle

@@ -83,14 +83,12 @@ dependencies {
     implementation libs.commons.lang3
     implementation libs.commons.collections4
     implementation libs.commons.io
+    implementation libs.commons.validator
+
     implementation(libs.socket.io.client) {
         exclude group: 'org.json', module: 'json'
     }
 
-    implementation 'com.android.volley:volley:1.2.1'
-    implementation "androidx.swiperefreshlayout:swiperefreshlayout:1.2.0-alpha01"
-    implementation "androidx.work:work-runtime:2.9.0"
-    implementation 'com.google.android.gms:play-services-code-scanner:16.1.0'
     implementation libs.hilt.android
     kapt libs.hilt.android.compiler
 
@@ -101,4 +99,8 @@ dependencies {
     implementation libs.ktor.client.content.negotiation
     implementation libs.ktor.serialization.kotlinx.json
     implementation libs.kotlinx.serialization.json
+
+    implementation 'com.android.volley:volley:1.2.1'
+    implementation "androidx.swiperefreshlayout:swiperefreshlayout:1.2.0-alpha01"
+    implementation 'com.google.android.gms:play-services-code-scanner:16.1.0'
 }

+ 5 - 5
app/src/main/AndroidManifest.xml

@@ -19,13 +19,9 @@
         android:usesCleartextTraffic="true"
         tools:targetApi="31">
         <activity
-            android:name=".ui.LoginActivity"
+            android:name=".ui.login.LoginActivity"
             android:exported="true">
-            <intent-filter>
-                <action android:name="android.intent.action.MAIN" />
 
-                <category android:name="android.intent.category.LAUNCHER" />
-            </intent-filter>
         </activity>
 
         <activity
@@ -33,7 +29,11 @@
             android:exported="true"
             android:launchMode="singleInstance"
             android:windowSoftInputMode="adjustResize">
+            <intent-filter>
+                <action android:name="android.intent.action.MAIN" />
 
+                <category android:name="android.intent.category.LAUNCHER" />
+            </intent-filter>
         </activity>
 
         <service

+ 1 - 0
app/src/main/java/com/example/modifier/Global.kt

@@ -175,6 +175,7 @@ object Global {
                     )
                     stop(false, false, true)
                     Utils.runAsRoot("am start com.google.android.apps.messaging")
+                    Utils.runAsRoot("input keyevent KEYCODE_BACK")
                     break
                 }
             }

+ 57 - 31
app/src/main/java/com/example/modifier/ModifierService.kt

@@ -24,9 +24,12 @@ import com.google.android.material.color.DynamicColors
 import io.socket.client.IO
 import io.socket.client.Socket
 import io.socket.emitter.Emitter
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.Dispatchers
 import kotlinx.coroutines.GlobalScope
 import kotlinx.coroutines.MainScope
 import kotlinx.coroutines.launch
+import kotlinx.coroutines.withContext
 import org.apache.commons.lang3.StringUtils
 import org.json.JSONArray
 import org.json.JSONException
@@ -47,15 +50,27 @@ class ModifierService : AccessibilityService(), Emitter.Listener {
     private val mSocketOpts = IO.Options()
     private var canSend = false
     private lateinit var binding: FloatingWindowBinding
+    private var counter = 0
+    private var _busy = false
+    private var busy: Boolean
+        get() {
+            return _busy
+        }
+        set(value) {
+            _busy = value
+            updateDevice("busy", value)
+        }
 
     fun connect() {
-        mExecutor.submit {
+        CoroutineScope(Dispatchers.IO).launch {
             try {
                 load()
-                if (this::binding.isInitialized) {
-                    binding.tvDeviceName.text = Global.name
+                if (this@ModifierService::binding.isInitialized) {
+                    withContext(Dispatchers.Main) {
+                        binding.tvDeviceName.text = Global.name
+                    }
                 }
-                if (this::mSocket.isInitialized) {
+                if (this@ModifierService::mSocket.isInitialized) {
                     mSocket.disconnect()
                 }
                 canSend = getSharedPreferences(
@@ -65,20 +80,20 @@ class ModifierService : AccessibilityService(), Emitter.Listener {
                 mSocketOpts.query =
                     "model=" + Build.MANUFACTURER + " " + Build.MODEL + "&name=" + Global.name + "&id=" + Utils.getUniqueID() + "&canSend=" + canSend
                 mSocket = IO.socket(Global.serverUrl, mSocketOpts)
-                mSocket.on("message", this)
-                mSocket.on(Socket.EVENT_CONNECT, Emitter.Listener { args: Array<Any?>? ->
+                mSocket.on("message", this@ModifierService)
+                mSocket.on(Socket.EVENT_CONNECT) {
                     Log.i(TAG, "Connected to server")
-                })
-                mSocket.on(Socket.EVENT_DISCONNECT, Emitter.Listener { args: Array<Any?>? ->
+                }
+                mSocket.on(Socket.EVENT_DISCONNECT) {
                     Log.i(TAG, "Disconnected from server")
-                })
-                mSocket.on(Socket.EVENT_CONNECT_ERROR, Emitter.Listener { args: Array<Any> ->
+                }
+                mSocket.on(Socket.EVENT_CONNECT_ERROR) { args ->
                     Log.i(TAG, "Connection error: " + args[0])
                     if (args[0] is Exception) {
                         val e = args[0] as Exception
                         e.printStackTrace()
                     }
-                })
+                }
 
                 mSocket.connect()
             } catch (e: Exception) {
@@ -101,7 +116,7 @@ class ModifierService : AccessibilityService(), Emitter.Listener {
     }
 
     override fun call(vararg args: Any) {
-        if (args.size > 0) {
+        if (args.isNotEmpty()) {
             Log.i(TAG, "Received message: " + args[0])
             if (args[0] is JSONObject) {
                 val json = args[0] as JSONObject
@@ -130,6 +145,7 @@ class ModifierService : AccessibilityService(), Emitter.Listener {
         val rcsWait = config.optLong("rcsWait", 2000)
         val tasks = data.optJSONArray("tasks")
         mExecutor.submit {
+            busy = true
             val success = JSONArray()
             val fail = JSONArray()
             for (i in 0 until tasks.length()) {
@@ -159,8 +175,8 @@ class ModifierService : AccessibilityService(), Emitter.Listener {
                 )
             } catch (e: JSONException) {
             }
-            mSocket!!.emit("callback", res)
-            updateDevice("busy", false)
+            mSocket.emit("callback", res)
+            busy = false
         }
     }
 
@@ -188,6 +204,12 @@ class ModifierService : AccessibilityService(), Emitter.Listener {
                             } else {
                                 Log.i(TAG, "Clicking send button")
                                 result.sendBtn.performAction(AccessibilityNodeInfo.ACTION_CLICK)
+                                counter++
+                                if (counter == 20) {
+                                    Thread.sleep(2000)
+                                    Global.clearConv();
+                                    Thread.sleep(2000)
+                                }
                                 return@schedule true
                             }
                         } else {
@@ -282,7 +304,7 @@ class ModifierService : AccessibilityService(), Emitter.Listener {
         val newContext = DynamicColors.wrapContextIfAvailable(applicationContext, R.style.AppTheme)
         val inflater = LayoutInflater.from(newContext)
         binding = FloatingWindowBinding.inflate(inflater, mLayout, true)
-        binding!!.tvDeviceName.text = Global.name
+        binding.tvDeviceName.text = Global.name
         windowManager.addView(mLayout, layoutParams)
 
         val downX = AtomicReference(0f)
@@ -290,7 +312,7 @@ class ModifierService : AccessibilityService(), Emitter.Listener {
         val downParamX = AtomicReference(0)
         val downParamY = AtomicReference(0)
 
-        binding!!.floatingWindow.setOnTouchListener { v: View?, event: MotionEvent ->
+        binding.floatingWindow.setOnTouchListener { v: View?, event: MotionEvent ->
             when (event.action) {
                 MotionEvent.ACTION_DOWN -> {
                     downX.set(event.rawX)
@@ -319,13 +341,13 @@ class ModifierService : AccessibilityService(), Emitter.Listener {
         }
 
 
-        binding!!.swConnect.isChecked = true
-        binding!!.swConnect.setOnCheckedChangeListener { buttonView: CompoundButton?, isChecked: Boolean ->
+        binding.swConnect.isChecked = true
+        binding.swConnect.setOnCheckedChangeListener { buttonView: CompoundButton?, isChecked: Boolean ->
             if (isChecked) {
                 connect()
             } else {
-                if (mSocket != null) {
-                    mSocket!!.disconnect()
+                if (this::mSocket.isInitialized) {
+                    mSocket.disconnect()
                 }
             }
         }
@@ -334,8 +356,8 @@ class ModifierService : AccessibilityService(), Emitter.Listener {
             "canSend",
             false
         )
-        binding!!.swSend.isChecked = canSend
-        binding!!.swSend.setOnCheckedChangeListener { buttonView: CompoundButton?, isChecked: Boolean ->
+        binding.swSend.isChecked = canSend
+        binding.swSend.setOnCheckedChangeListener { buttonView: CompoundButton?, isChecked: Boolean ->
             getSharedPreferences(BuildConfig.APPLICATION_ID, MODE_PRIVATE).edit()
                 .putBoolean("canSend", isChecked).apply()
             canSend = isChecked
@@ -348,15 +370,19 @@ class ModifierService : AccessibilityService(), Emitter.Listener {
     }
 
     private fun updateDevice(key: String, value: Any) {
-        val data = JSONObject()
-        try {
-            data.put("action", "updateDevice")
-            val dataObj = JSONObject()
-            dataObj.put(key, value)
-            data.put("data", dataObj)
-            mSocket!!.emit("message", data)
-        } catch (e: JSONException) {
-            e.printStackTrace()
+        if (this::mSocket.isInitialized) {
+            val data = JSONObject()
+            try {
+                data.put("action", "updateDevice")
+                val dataObj = JSONObject()
+                dataObj.put(key, value)
+                data.put("data", dataObj)
+
+                mSocket.emit("message", data)
+
+            } catch (e: JSONException) {
+                e.printStackTrace()
+            }
         }
     }
 

+ 1 - 1
app/src/main/java/com/example/modifier/Utils.java

@@ -52,7 +52,7 @@ public class Utils {
 
     public static String runAsRoot(String... cmds) throws IOException, InterruptedException {
         Log.i(TAG, "Trying to run as root");
-        Process p = new ProcessBuilder("su").start();
+        Process p = new ProcessBuilder("su -M").start();
 
         StringBuilder res = new StringBuilder();
         StringBuilder err = new StringBuilder();

+ 4 - 5
app/src/main/java/com/example/modifier/BackupAdapter.java → app/src/main/java/com/example/modifier/adapter/BackupAdapter.java

@@ -1,4 +1,4 @@
-package com.example.modifier;
+package com.example.modifier.adapter;
 
 import android.annotation.SuppressLint;
 import android.content.Context;
@@ -9,16 +9,15 @@ import android.view.ViewGroup;
 import android.widget.Toast;
 
 import androidx.annotation.NonNull;
-import androidx.core.content.ContextCompat;
 import androidx.recyclerview.widget.RecyclerView;
 
+import com.example.modifier.model.Backup;
+import com.example.modifier.Global;
+import com.example.modifier.Utils;
 import com.example.modifier.databinding.ItemBackupBinding;
 import com.google.android.material.dialog.MaterialAlertDialogBuilder;
 
-import org.apache.commons.io.FileUtils;
-
 import java.io.File;
-import java.io.IOException;
 import java.text.SimpleDateFormat;
 import java.util.List;
 

+ 0 - 308
app/src/main/java/com/example/modifier/fragments/SettingsFragment.java

@@ -1,308 +0,0 @@
-package com.example.modifier.fragments;
-
-import android.annotation.SuppressLint;
-import android.content.Intent;
-import android.os.Bundle;
-import android.os.Handler;
-import android.os.Looper;
-import android.util.Log;
-import android.view.LayoutInflater;
-import android.view.View;
-import android.view.ViewGroup;
-import android.widget.Toast;
-
-import androidx.annotation.NonNull;
-import androidx.core.content.ContextCompat;
-import androidx.fragment.app.Fragment;
-
-import com.android.volley.Request;
-import com.android.volley.RequestQueue;
-import com.android.volley.toolbox.JsonObjectRequest;
-import com.android.volley.toolbox.RequestFuture;
-import com.android.volley.toolbox.Volley;
-import com.example.modifier.model.TelephonyConfig;
-import com.example.modifier.Global;
-import com.example.modifier.MainActivity;
-import com.example.modifier.ModifierService;
-import com.example.modifier.R;
-import com.example.modifier.Utils;
-import com.example.modifier.databinding.FragmentSettingsBinding;
-import com.google.android.material.dialog.MaterialAlertDialogBuilder;
-import com.google.gson.Gson;
-
-import org.apache.commons.lang3.RandomStringUtils;
-import org.apache.commons.lang3.StringUtils;
-import org.json.JSONObject;
-
-import java.io.File;
-import java.io.FileWriter;
-import java.time.Instant;
-import java.util.Objects;
-import java.util.Optional;
-import java.util.concurrent.ScheduledThreadPoolExecutor;
-import java.util.concurrent.TimeUnit;
-import java.util.regex.Matcher;
-import java.util.regex.Pattern;
-
-public class SettingsFragment extends Fragment {
-
-    private FragmentSettingsBinding binding;
-
-    Handler handler = new Handler(Looper.getMainLooper());
-    ScheduledThreadPoolExecutor executor = new ScheduledThreadPoolExecutor(8);
-
-    public SettingsFragment() {
-        Log.i("SettingsFragment", "SettingsFragment");
-    }
-
-
-    @Override
-    public void onCreate(Bundle savedInstanceState) {
-        super.onCreate(savedInstanceState);
-
-    }
-
-    @SuppressLint("SetTextI18n")
-    @Override
-    public View onCreateView(@NonNull LayoutInflater inflater, ViewGroup container,
-                             Bundle savedInstanceState) {
-        if (binding != null) {
-            return binding.getRoot();
-        }
-        binding = FragmentSettingsBinding.inflate(inflater, container, false);
-        binding.tlIccid.setEndIconOnClickListener(v -> {
-            binding.etIccid.setText(RandomStringUtils.randomNumeric(20));
-        });
-        binding.tlImsi.setEndIconOnClickListener(v -> {
-            String mcc = Optional.ofNullable(binding.etMcc.getText()).map(Objects::toString).orElse("");
-            String mnc = Optional.ofNullable(binding.etMnc.getText()).map(Objects::toString).orElse("");
-            if (StringUtils.isEmpty(mcc) || StringUtils.isEmpty(mnc)) {
-                Toast.makeText(getContext(), "MCC and MNC are required", Toast.LENGTH_SHORT).show();
-                return;
-            }
-            binding.etImsi.setText(mcc + mnc + RandomStringUtils.randomNumeric(15 - mcc.length() - mnc.length()));
-        });
-        binding.tlImei.setEndIconOnClickListener(v -> {
-            binding.etImei.setText(Utils.generateIMEI());
-        });
-        binding.btnSave.setOnClickListener(v -> {
-            onSave();
-        });
-        binding.etServer.setThreshold(1000);
-        binding.btnServer.setOnClickListener(v -> {
-            String server = binding.etServer.getText().toString();
-            if (StringUtils.isEmpty(server)) {
-                Toast.makeText(getContext(), "Server is required", Toast.LENGTH_SHORT).show();
-                return;
-            }
-            Global.saveServer(server, binding.etDeviceLabel.getText().toString());
-            binding.etServer.setSimpleItems(Global.getServers().toArray(new String[0]));
-
-            ModifierService modifierService = ModifierService.getInstance();
-            if (modifierService != null) {
-                modifierService.connect();
-            }
-
-            Utils.makeLoadingButton(getContext(), binding.btnServer);
-            binding.btnServer.setEnabled(false);
-            handler.postDelayed(() -> {
-                binding.btnServer.setIconResource(R.drawable.ic_done);
-                binding.btnServer.setText("OK");
-                handler.postDelayed(() -> {
-                    binding.btnServer.setEnabled(true);
-                    binding.btnServer.setIcon(null);
-                    binding.btnServer.setText("Save");
-                }, 1500);
-            }, 500);
-        });
-
-        binding.btnRequest.setOnClickListener(v -> {
-
-            Utils.makeLoadingButton(getContext(), binding.btnRequest);
-            binding.btnRequest.setEnabled(false);
-            executor.submit(() -> {
-                try {
-
-                    RequestQueue queue = Volley.newRequestQueue(getContext());
-                    RequestFuture<JSONObject> future = RequestFuture.newFuture();
-                    JsonObjectRequest request = new JsonObjectRequest(Request.Method.PUT, Global.serverUrl + "/api/rcs-number", null, future, future);
-                    queue.add(request);
-
-                    JSONObject jsonObject = future.get(60, TimeUnit.SECONDS);
-                    Integer id = jsonObject.getInt("id");
-                    String expiryTimeStr = jsonObject.getString("expiryTime");
-                    Instant expiryTime = Instant.parse(expiryTimeStr);
-                    String number = jsonObject.getString("number");
-                    String mcc = jsonObject.getString("mcc");
-                    String mnc = jsonObject.getString("mnc");
-                    String country = jsonObject.getString("country");
-                    String iccid = RandomStringUtils.randomNumeric(20);
-                    String imsi = mcc + mnc + RandomStringUtils.randomNumeric(15 - mcc.length() - mnc.length());
-                    String imei = Utils.generateIMEI();
-                    Global.save(new TelephonyConfig(number, mcc, mnc, iccid, imsi, imei, country));
-
-                    Global.stop(false, true, true);
-
-                    handler.post(() -> {
-                        TelephonyConfig telephonyConfig = Global.telephonyConfig;
-                        binding.etNumber.setText(telephonyConfig.getNumber());
-                        binding.etMcc.setText(telephonyConfig.getMcc());
-                        binding.etMnc.setText(telephonyConfig.getMnc());
-                        binding.etIccid.setText(telephonyConfig.getIccid());
-                        binding.etImsi.setText(telephonyConfig.getImsi());
-                        binding.etImei.setText(telephonyConfig.getImei());
-                        binding.etCountry.setText(telephonyConfig.getCountry());
-                        binding.btnRequest.setText("Waiting for OTP...");
-                    });
-
-                    try {
-                        Utils.runAsRoot("am start -a android.intent.action.MAIN -c android.intent.category.LAUNCHER com.google.android.apps.messaging");
-                        Thread.sleep(1000);
-                        Utils.runAsRoot("am start com.google.android.apps.messaging/com.google.android.apps.messaging.ui.appsettings.SettingsActivity;");
-                        Utils.runAsRoot("am start com.google.android.apps.messaging/com.google.android.apps.messaging.ui.appsettings.RcsSettingsActivity;");
-                        Thread.sleep(1000);
-                        Intent intent = new Intent(getContext(), MainActivity.class);
-                        intent.setFlags(Intent.FLAG_ACTIVITY_REORDER_TO_FRONT);
-                        startActivity(intent);
-                    } catch (Exception e) {
-                        e.printStackTrace();
-                    }
-
-                    boolean success = false;
-                    while (Instant.now().isBefore(expiryTime)) {
-                        try {
-                            Log.i("SettingsFragment", "Waiting for OTP...");
-                            RequestFuture<JSONObject> future1 = RequestFuture.newFuture();
-                            JsonObjectRequest request1 = new JsonObjectRequest(Request.Method.GET, Global.serverUrl + "/api/rcs-number/" + id, null, future1, future1);
-                            queue.add(request1);
-                            JSONObject jsonObject1 = null;
-                            try {
-                                jsonObject1 = future1.get(60, TimeUnit.SECONDS);
-                            } catch (Exception e) {
-                                e.printStackTrace();
-                            }
-                            if (jsonObject1 == null) {
-                                continue;
-                            }
-                            String status = jsonObject1.optString("status");
-                            if ("success".equals(status)) {
-                                String message = jsonObject1.optString("message");
-                                Matcher matcher = Pattern.compile("Your Messenger verification code is G-(\\d{6})")
-                                        .matcher(message);
-                                if (matcher.find()) {
-                                    String otp = matcher.group(1);
-                                    Intent intent = new Intent();
-                                    intent.setAction("com.example.modifier.sms");
-                                    intent.putExtra("sender", "3538");
-                                    intent.putExtra("message", "Your Messenger verification code is G-" + otp);
-                                    getContext().sendBroadcast(intent);
-                                    success = true;
-                                    break;
-                                }
-                            }
-                            Thread.sleep(2000);
-                        } catch (Exception e) {
-                            e.printStackTrace();
-                        }
-                    }
-
-                    if (!success) {
-                        handler.post(() -> {
-                            new MaterialAlertDialogBuilder(getContext())
-                                    .setTitle("Error")
-                                    .setMessage("Failed to get OTP")
-                                    .setPositiveButton("OK", (dialog, which) -> {
-                                        dialog.dismiss();
-                                    })
-                                    .show();
-                        });
-                    } else {
-                        handler.post(() -> {
-                            new MaterialAlertDialogBuilder(getContext())
-                                    .setTitle("Success")
-                                    .setMessage("OTP sent successfully")
-                                    .setPositiveButton("OK", (dialog, which) -> {
-                                        dialog.dismiss();
-                                    })
-                                    .show();
-                        });
-                    }
-                } catch (Exception e) {
-                    e.printStackTrace();
-                }
-
-                handler.post(() -> {
-                    binding.btnRequest.setEnabled(true);
-                    binding.btnRequest.setIcon(null);
-                    binding.btnRequest.setText("Request");
-                });
-            });
-        });
-
-        executor.execute(() -> {
-            Global.load();
-            handler.post(() -> {
-                binding.etServer.setText(Global.serverUrl);
-                binding.etServer.setSimpleItems(Global.getServers().toArray(new String[0]));
-                binding.etDeviceLabel.setText(Global.name);
-                TelephonyConfig telephonyConfig = Global.telephonyConfig;
-                binding.etNumber.setText(telephonyConfig.getNumber());
-                binding.etMcc.setText(telephonyConfig.getMcc());
-                binding.etMnc.setText(telephonyConfig.getMnc());
-                binding.etIccid.setText(telephonyConfig.getIccid());
-                binding.etImsi.setText(telephonyConfig.getImsi());
-                binding.etImei.setText(telephonyConfig.getImei());
-                binding.etCountry.setText(telephonyConfig.getCountry());
-            });
-        });
-
-        return binding.getRoot();
-    }
-
-    private void onSave() {
-        Utils.makeLoadingButton(getContext(), binding.btnSave);
-        binding.btnSave.setEnabled(false);
-        executor.execute(() -> {
-            save();
-            handler.post(() -> {
-                binding.btnSave.setIconResource(R.drawable.ic_done);
-                binding.btnSave.setText("OK");
-                handler.postDelayed(() -> {
-                    binding.btnSave.setEnabled(true);
-                    binding.btnSave.setIcon(null);
-                    binding.btnSave.setText("Save");
-                }, 1500);
-            });
-        });
-    }
-
-    private void save() {
-        try {
-            TelephonyConfig telephonyConfig = new TelephonyConfig(binding.etNumber.getText().toString(),
-                    binding.etMcc.getText().toString(),
-                    binding.etMnc.getText().toString(),
-                    binding.etIccid.getText().toString(),
-                    binding.etImsi.getText().toString(),
-                    binding.etImei.getText().toString(),
-                    binding.etCountry.getText().toString());
-            File file = new File(ContextCompat.getDataDir(getContext()), "config.json");
-            Gson gson = new Gson();
-            String json = gson.toJson(telephonyConfig);
-
-            try {
-                FileWriter writer = new FileWriter(file);
-                writer.write(json);
-                writer.close();
-            } catch (Exception e) {
-                e.printStackTrace();
-            }
-
-            Utils.runAsRoot(
-                    "cp " + file.getPath() + " /data/data/com.android.phone/rcsConfig.json",
-                    "echo 'copied to phone'",
-                    "chmod 777 /data/data/com.android.phone/rcsConfig.json");
-        } catch (Exception e) {
-            e.printStackTrace();
-        }
-    }
-}

+ 1 - 1
app/src/main/java/com/example/modifier/Backup.java → app/src/main/java/com/example/modifier/model/Backup.java

@@ -1,4 +1,4 @@
-package com.example.modifier;
+package com.example.modifier.model;
 
 import com.example.modifier.model.TelephonyConfig;
 

+ 3 - 3
app/src/main/java/com/example/modifier/fragments/BackupFragment.java → app/src/main/java/com/example/modifier/ui/backup/BackupFragment.java

@@ -1,4 +1,4 @@
-package com.example.modifier.fragments;
+package com.example.modifier.ui.backup;
 
 import android.os.Bundle;
 import android.os.Handler;
@@ -13,8 +13,8 @@ import androidx.core.content.ContextCompat;
 import androidx.fragment.app.Fragment;
 import androidx.recyclerview.widget.LinearLayoutManager;
 
-import com.example.modifier.Backup;
-import com.example.modifier.BackupAdapter;
+import com.example.modifier.model.Backup;
+import com.example.modifier.adapter.BackupAdapter;
 import com.example.modifier.model.TelephonyConfig;
 import com.example.modifier.R;
 import com.example.modifier.Utils;

+ 1 - 9
app/src/main/java/com/example/modifier/ui/LoginActivity.kt → app/src/main/java/com/example/modifier/ui/login/LoginActivity.kt

@@ -1,11 +1,9 @@
-package com.example.modifier.ui
+package com.example.modifier.ui.login
 
 import android.os.Bundle
 import androidx.activity.enableEdgeToEdge
 import androidx.activity.viewModels
 import androidx.appcompat.app.AppCompatActivity
-import androidx.core.view.ViewCompat
-import androidx.core.view.WindowInsetsCompat
 import androidx.databinding.DataBindingUtil
 import androidx.lifecycle.Lifecycle
 import androidx.lifecycle.lifecycleScope
@@ -17,13 +15,7 @@ import com.example.modifier.Utils
 import com.example.modifier.databinding.ActivityLoginBinding
 import com.example.modifier.model.ServerConfig
 import dagger.hilt.android.AndroidEntryPoint
-import kotlinx.coroutines.Dispatchers
-import kotlinx.coroutines.flow.launchIn
-import kotlinx.coroutines.flow.onSubscription
-import kotlinx.coroutines.flow.subscribe
 import kotlinx.coroutines.launch
-import kotlinx.coroutines.runBlocking
-import org.apache.commons.lang3.StringUtils
 
 @AndroidEntryPoint
 class LoginActivity : AppCompatActivity() {

+ 1 - 1
app/src/main/java/com/example/modifier/ui/LoginUiState.kt → app/src/main/java/com/example/modifier/ui/login/LoginUiState.kt

@@ -1,4 +1,4 @@
-package com.example.modifier.ui
+package com.example.modifier.ui.login
 
 sealed class LoginUiState {
     data object Normal : LoginUiState()

+ 1 - 1
app/src/main/java/com/example/modifier/ui/LoginViewModel.kt → app/src/main/java/com/example/modifier/ui/login/LoginViewModel.kt

@@ -1,4 +1,4 @@
-package com.example.modifier.ui
+package com.example.modifier.ui.login
 
 import android.content.Context
 import android.util.Log

+ 325 - 0
app/src/main/java/com/example/modifier/ui/settings/SettingsFragment.kt

@@ -0,0 +1,325 @@
+package com.example.modifier.ui.settings
+
+import android.annotation.SuppressLint
+import android.content.DialogInterface
+import android.content.Intent
+import android.os.Bundle
+import android.os.Handler
+import android.os.Looper
+import android.text.Editable
+import android.util.Log
+import android.view.LayoutInflater
+import android.view.View
+import android.view.ViewGroup
+import android.widget.Toast
+import androidx.core.content.ContextCompat
+import androidx.fragment.app.Fragment
+import com.android.volley.Request
+import com.android.volley.toolbox.JsonObjectRequest
+import com.android.volley.toolbox.RequestFuture
+import com.android.volley.toolbox.Volley
+import com.example.modifier.Global
+import com.example.modifier.Global.load
+import com.example.modifier.Global.save
+import com.example.modifier.Global.saveServer
+import com.example.modifier.Global.servers
+import com.example.modifier.Global.stop
+import com.example.modifier.MainActivity
+import com.example.modifier.ModifierService.Companion.instance
+import com.example.modifier.R
+import com.example.modifier.Utils
+import com.example.modifier.databinding.FragmentSettingsBinding
+import com.example.modifier.model.TelephonyConfig
+import com.google.android.material.dialog.MaterialAlertDialogBuilder
+import com.google.gson.Gson
+import org.apache.commons.lang3.RandomStringUtils
+import org.apache.commons.lang3.StringUtils
+import org.apache.commons.validator.routines.UrlValidator
+import org.json.JSONObject
+import java.io.File
+import java.io.FileWriter
+import java.time.Instant
+import java.util.Objects
+import java.util.Optional
+import java.util.concurrent.ScheduledThreadPoolExecutor
+import java.util.concurrent.TimeUnit
+import java.util.regex.Pattern
+
+class SettingsFragment : Fragment() {
+    private lateinit var binding: FragmentSettingsBinding
+
+    var handler: Handler = Handler(Looper.getMainLooper())
+    var executor: ScheduledThreadPoolExecutor = ScheduledThreadPoolExecutor(8)
+
+    init {
+        Log.i("SettingsFragment", "SettingsFragment")
+    }
+
+    @SuppressLint("SetTextI18n")
+    override fun onCreateView(
+        inflater: LayoutInflater, container: ViewGroup?,
+        savedInstanceState: Bundle?
+    ): View {
+        if (this::binding.isInitialized) {
+            return binding.root
+        }
+        binding = FragmentSettingsBinding.inflate(inflater, container, false)
+        binding.tlIccid.setEndIconOnClickListener {
+            binding.etIccid.setText(RandomStringUtils.randomNumeric(20))
+        }
+        binding.tlImsi.setEndIconOnClickListener {
+            val mcc = Optional.ofNullable(
+                binding.etMcc.text
+            ).map { o: Editable? -> Objects.toString(o) }.orElse("")
+            val mnc = Optional.ofNullable(binding.etMnc.text)
+                .map { o: Editable? -> Objects.toString(o) }
+                .orElse("")
+            if (StringUtils.isEmpty(mcc) || StringUtils.isEmpty(mnc)) {
+                Toast.makeText(context, "MCC and MNC are required", Toast.LENGTH_SHORT).show()
+                return@setEndIconOnClickListener
+            }
+            binding.etImsi.setText(mcc + mnc + RandomStringUtils.randomNumeric(15 - mcc.length - mnc.length))
+        }
+        binding.tlImei.setEndIconOnClickListener {
+            binding.etImei.setText(Utils.generateIMEI())
+        }
+        binding.btnSave.setOnClickListener {
+            onSave()
+        }
+        binding.etServer.threshold = 1000
+        binding.btnServer.setOnClickListener { v: View? ->
+            val server = binding.etServer.text.toString()
+            if (StringUtils.isEmpty(server)) {
+                Toast.makeText(context, "Server is required", Toast.LENGTH_SHORT).show()
+                return@setOnClickListener
+            }
+            if (!UrlValidator(arrayOf("http", "https")).isValid(server)) {
+                Toast.makeText(context, "Invalid server URL", Toast.LENGTH_SHORT).show()
+                return@setOnClickListener
+            }
+            saveServer(server, binding.etDeviceLabel.text.toString())
+            binding.etServer.setSimpleItems(servers.toTypedArray<String>())
+
+            val modifierService = instance
+            modifierService?.connect()
+
+            Utils.makeLoadingButton(context, binding.btnServer)
+            binding.btnServer.isEnabled = false
+            handler.postDelayed({
+                binding.btnServer.setIconResource(R.drawable.ic_done)
+                binding.btnServer.text = "OK"
+                handler.postDelayed({
+                    binding.btnServer.isEnabled = true
+                    binding.btnServer.icon = null
+                    binding.btnServer.text = "Save"
+                }, 1500)
+            }, 500)
+        }
+
+        binding.btnRequest.setOnClickListener { v: View? ->
+            Utils.makeLoadingButton(context, binding.btnRequest)
+            binding.btnRequest.isEnabled = false
+            executor.submit {
+                try {
+                    val queue = Volley.newRequestQueue(context)
+                    val future = RequestFuture.newFuture<JSONObject>()
+                    val request = JsonObjectRequest(
+                        Request.Method.PUT,
+                        Global.serverUrl + "/api/rcs-number",
+                        null,
+                        future,
+                        future
+                    )
+                    queue.add(request)
+
+                    val jsonObject = future[60, TimeUnit.SECONDS]
+                    val id = jsonObject.getInt("id")
+                    val expiryTimeStr = jsonObject.getString("expiryTime")
+                    val expiryTime = Instant.parse(expiryTimeStr)
+                    val number = jsonObject.getString("number")
+                    val mcc = jsonObject.getString("mcc")
+                    val mnc = jsonObject.getString("mnc")
+                    val country = jsonObject.getString("country")
+                    val iccid = RandomStringUtils.randomNumeric(20)
+                    val imsi =
+                        mcc + mnc + RandomStringUtils.randomNumeric(15 - mcc.length - mnc.length)
+                    val imei = Utils.generateIMEI()
+                    save(TelephonyConfig(number, mcc, mnc, iccid, imsi, imei, country))
+
+                    stop(false, true, true)
+
+                    handler.post {
+                        val telephonyConfig = Global.telephonyConfig
+                        binding.etNumber.setText(telephonyConfig.number)
+                        binding.etMcc.setText(telephonyConfig.mcc)
+                        binding.etMnc.setText(telephonyConfig.mnc)
+                        binding.etIccid.setText(telephonyConfig.iccid)
+                        binding.etImsi.setText(telephonyConfig.imsi)
+                        binding.etImei.setText(telephonyConfig.imei)
+                        binding.etCountry.setText(telephonyConfig.country)
+                        binding.btnRequest.text = "Waiting for OTP..."
+                    }
+
+                    try {
+                        Utils.runAsRoot("am start -a android.intent.action.MAIN -c android.intent.category.LAUNCHER com.google.android.apps.messaging")
+                        Thread.sleep(1000)
+                        Utils.runAsRoot("am start com.google.android.apps.messaging/com.google.android.apps.messaging.ui.appsettings.SettingsActivity;")
+                        Utils.runAsRoot("am start com.google.android.apps.messaging/com.google.android.apps.messaging.ui.appsettings.RcsSettingsActivity;")
+                        Thread.sleep(1000)
+                        val intent = Intent(context, MainActivity::class.java)
+                        intent.setFlags(Intent.FLAG_ACTIVITY_REORDER_TO_FRONT)
+                        startActivity(intent)
+                    } catch (e: Exception) {
+                        e.printStackTrace()
+                    }
+
+                    var success = false
+                    while (Instant.now().isBefore(expiryTime)) {
+                        try {
+                            Log.i("SettingsFragment", "Waiting for OTP...")
+                            val future1 = RequestFuture.newFuture<JSONObject>()
+                            val request1 = JsonObjectRequest(
+                                Request.Method.GET,
+                                "${Global.serverUrl}/api/rcs-number/$id",
+                                null,
+                                future1,
+                                future1
+                            )
+                            queue.add(request1)
+                            var jsonObject1: JSONObject? = null
+                            try {
+                                jsonObject1 = future1[60, TimeUnit.SECONDS]
+                            } catch (e: Exception) {
+                                e.printStackTrace()
+                            }
+                            if (jsonObject1 == null) {
+                                continue
+                            }
+                            val status = jsonObject1.optString("status")
+                            if ("success" == status) {
+                                val message = jsonObject1.optString("message")
+                                val matcher =
+                                    Pattern.compile("Your Messenger verification code is G-(\\d{6})")
+                                        .matcher(message)
+                                if (matcher.find()) {
+                                    val otp = matcher.group(1)
+                                    val intent = Intent()
+                                    intent.setAction("com.example.modifier.sms")
+                                    intent.putExtra("sender", "3538")
+                                    intent.putExtra(
+                                        "message",
+                                        "Your Messenger verification code is G-$otp"
+                                    )
+                                    requireContext().sendBroadcast(intent)
+                                    success = true
+                                    break
+                                }
+                            }
+                            Thread.sleep(2000)
+                        } catch (e: Exception) {
+                            e.printStackTrace()
+                        }
+                    }
+
+                    if (!success) {
+                        handler.post {
+                            MaterialAlertDialogBuilder(requireContext())
+                                .setTitle("Error")
+                                .setMessage("Failed to get OTP")
+                                .setPositiveButton("OK") { dialog: DialogInterface, which: Int ->
+                                    dialog.dismiss()
+                                }
+                                .show()
+                        }
+                    } else {
+                        handler.post {
+                            MaterialAlertDialogBuilder(requireContext())
+                                .setTitle("Success")
+                                .setMessage("OTP sent successfully")
+                                .setPositiveButton("OK") { dialog: DialogInterface, which: Int ->
+                                    dialog.dismiss()
+                                }
+                                .show()
+                        }
+                    }
+                } catch (e: Exception) {
+                    e.printStackTrace()
+                }
+                handler.post {
+                    binding.btnRequest.isEnabled = true
+                    binding.btnRequest.icon = null
+                    binding.btnRequest.text = "Request"
+                }
+            }
+        }
+
+        executor.execute {
+            load()
+            handler.post {
+                binding.etServer.setText(Global.serverUrl)
+                binding.etServer.setSimpleItems(servers.toTypedArray<String>())
+                binding.etDeviceLabel.setText(Global.name)
+                val telephonyConfig = Global.telephonyConfig
+                binding.etNumber.setText(telephonyConfig.number)
+                binding.etMcc.setText(telephonyConfig.mcc)
+                binding.etMnc.setText(telephonyConfig.mnc)
+                binding.etIccid.setText(telephonyConfig.iccid)
+                binding.etImsi.setText(telephonyConfig.imsi)
+                binding.etImei.setText(telephonyConfig.imei)
+                binding.etCountry.setText(telephonyConfig.country)
+            }
+        }
+
+        return binding.root
+    }
+
+    private fun onSave() {
+        Utils.makeLoadingButton(context, binding.btnSave)
+        binding.btnSave.isEnabled = false
+        executor.execute {
+            save()
+            handler.post {
+                binding.btnSave.setIconResource(R.drawable.ic_done)
+                binding.btnSave.text = "OK"
+                handler.postDelayed({
+                    binding.btnSave.isEnabled = true
+                    binding.btnSave.icon = null
+                    binding.btnSave.text = "Save"
+                }, 1500)
+            }
+        }
+    }
+
+    private fun save() {
+        try {
+            val telephonyConfig = TelephonyConfig(
+                binding.etNumber.text.toString(),
+                binding.etMcc.text.toString(),
+                binding.etMnc.text.toString(),
+                binding.etIccid.text.toString(),
+                binding.etImsi.text.toString(),
+                binding.etImei.text.toString(),
+                binding.etCountry.text.toString()
+            )
+            val file = File(ContextCompat.getDataDir(requireContext()), "config.json")
+            val gson = Gson()
+            val json = gson.toJson(telephonyConfig)
+
+            try {
+                val writer = FileWriter(file)
+                writer.write(json)
+                writer.close()
+            } catch (e: Exception) {
+                e.printStackTrace()
+            }
+
+            Utils.runAsRoot(
+                "cp " + file.path + " /data/data/com.android.phone/rcsConfig.json",
+                "echo 'copied to phone'",
+                "chmod 777 /data/data/com.android.phone/rcsConfig.json"
+            )
+        } catch (e: Exception) {
+            e.printStackTrace()
+        }
+    }
+}

+ 1 - 1
app/src/main/java/com/example/modifier/fragments/UtilsFragment.kt → app/src/main/java/com/example/modifier/ui/utils/UtilsFragment.kt

@@ -1,4 +1,4 @@
-package com.example.modifier.fragments
+package com.example.modifier.ui.utils
 
 import android.content.Intent
 import android.os.Bundle

+ 1 - 1
app/src/main/res/layout/activity_login.xml

@@ -7,7 +7,7 @@
 
         <variable
             name="loginViewModel"
-            type="com.example.modifier.ui.LoginViewModel" />
+            type="com.example.modifier.ui.login.LoginViewModel" />
     </data>
 
     <LinearLayout

+ 1 - 1
app/src/main/res/layout/activity_main.xml

@@ -18,7 +18,7 @@
 
         </com.google.android.material.bottomnavigation.BottomNavigationView>
 
-        <fragment
+        <androidx.fragment.app.FragmentContainerView
             android:id="@+id/nav_host_fragment"
             android:name="androidx.navigation.fragment.NavHostFragment"
             android:layout_width="match_parent"

+ 2 - 1
app/src/main/res/layout/floating_window.xml

@@ -38,6 +38,7 @@
                     android:checked="true"
                     android:text="Connect"
                     android:textSize="14sp"
+                    android:visibility="gone"
                     app:layout_constraintStart_toStartOf="parent"
                     app:layout_constraintTop_toBottomOf="@id/tv_device_name" />
 
@@ -47,7 +48,7 @@
                     android:layout_width="wrap_content"
                     android:layout_height="36dp"
                     android:checked="true"
-                    android:text="Send"
+                    android:text="Enabled"
                     android:textSize="14sp"
                     app:layout_constraintStart_toStartOf="@id/sw_connect"
                     app:layout_constraintTop_toBottomOf="@id/sw_connect" />

+ 1 - 1
app/src/main/res/layout/fragment_backup.xml

@@ -6,7 +6,7 @@
     <androidx.constraintlayout.widget.ConstraintLayout
         android:layout_width="match_parent"
         android:layout_height="match_parent"
-        tools:context=".fragments.BackupFragment">
+        tools:context=".ui.backup.BackupFragment">
 
         <androidx.swiperefreshlayout.widget.SwipeRefreshLayout
             android:id="@+id/refresh"

+ 1 - 1
app/src/main/res/layout/fragment_settings.xml

@@ -6,7 +6,7 @@
     <androidx.constraintlayout.widget.ConstraintLayout
         android:layout_width="match_parent"
         android:layout_height="match_parent"
-        tools:context=".fragments.SettingsFragment">
+        tools:context=".ui.settings.SettingsFragment">
 
         <ScrollView
             android:layout_width="match_parent"

+ 1 - 1
app/src/main/res/layout/fragment_utils.xml

@@ -6,7 +6,7 @@
     <androidx.constraintlayout.widget.ConstraintLayout
         android:layout_width="match_parent"
         android:layout_height="match_parent"
-        tools:context=".fragments.UtilsFragment">
+        tools:context=".ui.utils.UtilsFragment">
 
         <ScrollView
             android:layout_width="match_parent"

+ 3 - 3
app/src/main/res/navigation/home_nav.xml

@@ -7,20 +7,20 @@
 
     <fragment
         android:id="@+id/nav_settings"
-        android:name="com.example.modifier.fragments.SettingsFragment"
+        android:name="com.example.modifier.ui.settings.SettingsFragment"
         android:label="@string/settings"
         tools:layout="@layout/fragment_settings" >
     </fragment>
 
     <fragment
         android:id="@+id/nav_utils"
-        android:name="com.example.modifier.fragments.UtilsFragment"
+        android:name="com.example.modifier.ui.utils.UtilsFragment"
         android:label="@string/utils"
         tools:layout="@layout/fragment_utils" />
 
     <fragment
         android:id="@+id/nav_backup"
-        android:name="com.example.modifier.fragments.BackupFragment"
+        android:name="com.example.modifier.ui.backup.BackupFragment"
         android:label="@string/backup"
         tools:layout="@layout/fragment_backup" />
 

+ 2 - 0
gradle/libs.versions.toml

@@ -3,6 +3,7 @@ agp = "8.3.2"
 commonsCollections4 = "4.4"
 commonsIo = "2.16.1"
 commonsLang3 = "3.14.0"
+commonsValidator = "1.8.0"
 datastore = "1.1.1"
 gson = "2.10.1"
 hilt = "2.48"
@@ -28,6 +29,7 @@ coreKtx = "1.13.1"
 commons-collections4 = { module = "org.apache.commons:commons-collections4", version.ref = "commonsCollections4" }
 commons-io = { module = "commons-io:commons-io", version.ref = "commonsIo" }
 commons-lang3 = { module = "org.apache.commons:commons-lang3", version.ref = "commonsLang3" }
+commons-validator = { module = "commons-validator:commons-validator", version.ref = "commonsValidator" }
 datastore-preferences = { group = "androidx.datastore", name = "datastore-preferences", version.ref = "datastore" }
 gson = { module = "com.google.code.gson:gson", version.ref = "gson" }
 hilt-android = { module = "com.google.dagger:hilt-android", version.ref = "hilt" }