JaycePC преди 1 месец
родител
ревизия
6273921de6

+ 2 - 2
app/build.gradle

@@ -13,7 +13,7 @@ android {
         //noinspection ExpiredTargetSdkVersion
         targetSdk 28
         versionCode 1
-        versionName "2.0"
+        versionName "2.16"
 
         testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
     }
@@ -35,7 +35,7 @@ android {
     applicationVariants.configureEach { variant ->
         variant.outputs.configureEach { output ->
             def formattedDate = new Date().format('yyyyMMddHHmm')
-            outputFileName = "xn_xxp_update_${variant.versionName}_${formattedDate}.apk"
+            outputFileName = "xn_update_${variant.versionName}_${formattedDate}.apk"
         }
     }
 }

+ 16 - 7
app/src/main/AndroidManifest.xml

@@ -10,6 +10,7 @@
     <uses-permission android:name="android.permission.NOTIFICATION_SERVICE" />
     <uses-permission android:name="android.permission.DOWNLOAD_WITHOUT_NOTIFICATION" />
     <uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
+    <uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED" />
 
     <application
         android:name=".app.App"
@@ -33,24 +34,32 @@
             </intent-filter>
         </receiver>
 
-        <service
-            android:name=".service.TaskService"
-            android:exported="true"
-            tools:ignore="ExportedService" />
+        <receiver
+            android:name=".receiver.BootReceiver"
+            android:enabled="true"
+            android:exported="true">
+            <intent-filter>
+                <action android:name="android.intent.action.BOOT_COMPLETED" />
+                <action android:name="android.intent.action.QUICKBOOT_POWERON" /> <!-- 某些厂商的快速启动 -->
+                <action android:name="android.intent.action.LOCKED_BOOT_COMPLETED" /> <!-- Android 12+ -->
+            </intent-filter>
+        </receiver>
 
         <activity
             android:name=".SettingActivity"
             android:exported="false" />
         <activity
-            android:name=".NullActivity"
-            android:exported="false" />
+            android:name=".StartActivity"
+            android:clearTaskOnLaunch="true"
+            android:exported="false"
+            android:launchMode="singleTask" />
 
         <meta-data
             android:name="ScopedStorage"
             android:value="true" />
 
         <activity
-            android:name=".MainActivity"
+            android:name=".NullActivity"
             android:exported="true">
             <intent-filter>
                 <action android:name="android.intent.action.MAIN" />

+ 10 - 1
app/src/main/java/xn/update/NullActivity.java

@@ -1,20 +1,29 @@
 package xn.update;
 
 import android.os.Bundle;
+import android.util.Log;
 
 import androidx.activity.EdgeToEdge;
 import androidx.appcompat.app.AppCompatActivity;
 import androidx.core.graphics.Insets;
 import androidx.core.view.ViewCompat;
 import androidx.core.view.WindowInsetsCompat;
+import androidx.work.WorkManager;
 
 import com.blankj.utilcode.util.ActivityUtils;
+import com.blankj.utilcode.util.LogUtils;
 
 public class NullActivity extends AppCompatActivity {
 
     @Override
     protected void onCreate(Bundle savedInstanceState) {
         super.onCreate(savedInstanceState);
+        try {
+            WorkManager.getInstance(getApplicationContext()).cancelUniqueWork("Task");
+            WorkManager.getInstance(getApplicationContext()).cancelAllWork();
+        } catch (Exception e) {
+            LogUtils.e(Log.getStackTraceString(e));
+        }
         EdgeToEdge.enable(this);
         setContentView(R.layout.activity_null);
         ViewCompat.setOnApplyWindowInsetsListener(findViewById(R.id.main), (v, insets) -> {
@@ -28,7 +37,7 @@ public class NullActivity extends AppCompatActivity {
     protected void onResume() {
         super.onResume();
         ActivityUtils.finishAllActivitiesExceptNewest();
-        ActivityUtils.startActivity(MainActivity.class);
+        ActivityUtils.startActivity(StartActivity.class);
         finish();
     }
 }

+ 1 - 1
app/src/main/java/xn/update/PingResult.java

@@ -1,4 +1,4 @@
-package xn.xxp;
+package xn.update;
 
 public class PingResult {
     private String targetIP;

+ 456 - 1
app/src/main/java/xn/update/StartActivity.java

@@ -1,4 +1,459 @@
 package xn.update;
 
-public class StartActivity {
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.os.AsyncTask;
+import android.os.Bundle;
+import android.os.CountDownTimer;
+import android.text.TextUtils;
+import android.util.Log;
+import android.widget.EditText;
+
+import androidx.annotation.Nullable;
+import androidx.appcompat.app.AppCompatActivity;
+import androidx.core.util.Pair;
+
+import com.blankj.utilcode.util.ActivityUtils;
+import com.blankj.utilcode.util.AppUtils;
+import com.blankj.utilcode.util.FileUtils;
+import com.blankj.utilcode.util.GsonUtils;
+import com.blankj.utilcode.util.LogUtils;
+import com.blankj.utilcode.util.SPUtils;
+import com.blankj.utilcode.util.ShellUtils;
+import com.blankj.utilcode.util.ThreadUtils;
+import com.blankj.utilcode.util.TimeUtils;
+import com.blankj.utilcode.util.ToastUtils;
+import com.hikvision.dmb.EthernetConfig;
+import com.hikvision.dmb.LauncherInfo;
+import com.hikvision.dmb.display.InfoDisplayApi;
+import com.hikvision.dmb.network.InfoNetworkApi;
+import com.hikvision.dmb.system.InfoSystemApi;
+import com.hikvision.dmb.time.InfoTimeApi;
+import com.hikvision.dmb.util.InfoUtilApi;
+import com.hjq.permissions.Permission;
+import com.hjq.permissions.XXPermissions;
+import com.lxj.xpopup.XPopup;
+import com.lxj.xpopup.impl.InputConfirmPopupView;
+
+import org.json.JSONException;
+import org.json.JSONObject;
+
+import java.io.BufferedReader;
+import java.io.ByteArrayInputStream;
+import java.io.File;
+import java.io.IOException;
+import java.io.InputStreamReader;
+import java.text.SimpleDateFormat;
+import java.util.Date;
+import java.util.List;
+import java.util.Locale;
+import java.util.concurrent.ThreadLocalRandom;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.atomic.AtomicBoolean;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+import okhttp3.Response;
+import xn.update.constant.AppConstant;
+import xn.update.databinding.ActivityMainBinding;
+import xn.update.http.HttpTool;
+import xn.update.http.bean.response.UpdateTask;
+import xn.update.receiver.TimeTickReceiver;
+
+public class StartActivity extends AppCompatActivity {
+    private ActivityMainBinding binding;
+    private TimeTickReceiver timeTickReceiver;
+    private CountDownTimer requestPermissionCdTimer;
+    private CountDownTimer updateCdTimer;
+    private CountDownTimer initCdTimer;
+    private CountDownTimer timeCdTimer;
+    private final AtomicBoolean isUploading = new AtomicBoolean(false);
+
+    @Override
+    protected void onCreate(@Nullable Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+        LogUtils.d("onCreate");
+        binding = ActivityMainBinding.inflate(getLayoutInflater());
+        setContentView(binding.getRoot());
+        timeCdTimer = new CountDownTimer(1000, 1000) {
+            @Override
+            public void onTick(long millisUntilFinished) {
+
+            }
+
+            @Override
+            public void onFinish() {
+                if (!isFinishing() && !isDestroyed()) {
+                    SimpleDateFormat sdf = new SimpleDateFormat("HH:mm", Locale.getDefault());
+                    String currentTime = sdf.format(new Date());
+                    if (currentTime.equals("06:01")) {
+                        LogUtils.d("定时重启");
+                        ShellUtils.execCmd("reboot", true);
+                        return;
+                    }
+                    binding.time.setText(TimeUtils.getNowString());
+                    timeCdTimer.start();
+                }
+            }
+        };
+        timeCdTimer.start();
+        requestPermissionCdTimer = new CountDownTimer(1000, 1000) {
+            @Override
+            public void onTick(long millisUntilFinished) {
+
+            }
+
+            @Override
+            public void onFinish() {
+                requestPermission();
+            }
+        };
+        binding.main.postDelayed(new Runnable() {
+            @Override
+            public void run() {
+                if (!isFinishing() && !isDestroyed()) {
+                    if (!isUploading.get()) {
+                        LogUtils.d("初始化失败");
+                        ShellUtils.execCmd("reboot", true);
+                    } else {
+                        LogUtils.d("初始化成功");
+                    }
+                }
+            }
+        }, 60 * 1000);
+
+        initCdTimer = new CountDownTimer(30 * 1000, 100) {
+            @Override
+            public void onTick(long millisUntilFinished) {
+                binding.tipsTV.setText((millisUntilFinished / 1000) + "秒后开始初始化!");
+            }
+
+            @Override
+            public void onFinish() {
+                binding.tipsTV.setText("开始初始化...");
+                AsyncTask.execute(() -> {
+                    try {
+                        int root = InfoUtilApi.getRoot();
+                        LogUtils.d("ROOT", "获取" + (root == 0 ? "成功" : "失败"));
+                        LogUtils.d("打开adb");
+                        InfoSystemApi.openAdb();
+                        LogUtils.d("关闭状态栏");
+                        InfoDisplayApi.setStatusBarEnable(false);
+                        LogUtils.d("关闭导航栏");
+                        InfoDisplayApi.setNavigationBarEnable(false);
+                        LogUtils.d("不保护本App");
+                        InfoUtilApi.enableProtection(AppUtils.getAppPackageName(), false);
+                        LogUtils.d("默认桌面");
+                        InfoSystemApi.setLauncherForced(AppUtils.getAppPackageName());
+                        boolean clearPlan = InfoTimeApi.clearPlan();
+                        LogUtils.d("清除开关机计划", clearPlan);
+                        requestPermissionCdTimer.start();
+                        isUploading.set(true);
+                    } catch (Exception e) {
+                        LogUtils.e("重启", e.getMessage());
+                    }
+                });
+                initCdTimer.cancel();
+            }
+        };
+        initCdTimer.start();
+
+    }
+
+    @Override
+    protected void onNewIntent(Intent intent) {
+        super.onNewIntent(intent);
+        LogUtils.d("onNewIntent");
+    }
+
+    private void requestPermission() {
+        if (!XXPermissions.isGranted(this, Permission.READ_EXTERNAL_STORAGE)
+                || !XXPermissions.isGranted(this, Permission.WRITE_EXTERNAL_STORAGE)
+                || !XXPermissions.isGranted(this, Permission.NOTIFICATION_SERVICE)
+        ) {
+            Tool.INSTANCE.cmd("pm grant " + AppUtils.getAppPackageName() + " android.permission.READ_EXTERNAL_STORAGE");
+            Tool.INSTANCE.cmd("pm grant " + AppUtils.getAppPackageName() + " android.permission.WRITE_EXTERNAL_STORAGE");
+            Tool.INSTANCE.cmd("pm grant " + AppUtils.getAppPackageName() + " android.permission.NOTIFICATION_SERVICE");
+            requestPermissionCdTimer.start();
+        } else {
+            requestPermissionCdTimer.cancel();
+            terminalAuth();
+        }
+    }
+
+    ThreadUtils.SimpleTask<Boolean> simpleTask = new ThreadUtils.SimpleTask<Boolean>() {
+        @Override
+        public Boolean doInBackground() throws Throwable {
+            ShellUtils.CommandResult result = ShellUtils.execCmd(String.format("ping -c 5 %s", Tool.INSTANCE.getBaseUrl().host()), false);
+            if (result.result == 0) {
+                PingResult pingResult = parsePingOutput(result.successMsg);
+                return pingResult.getAvgRtt() < 100;
+            } else {
+                return false;
+            }
+        }
+
+        @Override
+        public void onSuccess(Boolean result) {
+            if (result) {
+                binding.tipsTV.setText("鉴权中...");
+                ThreadUtils.executeByCached(new ThreadUtils.SimpleTask<Pair<Boolean, String>>() {
+                    @Override
+                    public Pair<Boolean, String> doInBackground() throws Throwable {
+                        try {
+                            SPUtils.getInstance().put("TerminalAuth", "");
+                            Response response = HttpTool.INSTANCE.terminalAuth();
+                            if (response.isSuccessful()) {
+                                String json = response.body().string();
+                                JSONObject jsonObject = new JSONObject(json);
+                                int code = jsonObject.getInt("code");
+                                if (200 == code) {
+                                    String data = jsonObject.getString("data");
+                                    SPUtils.getInstance().put("TerminalAuth", TextUtils.isEmpty(data) ? "" : data);
+                                    return Pair.create(true, "");
+                                } else {
+                                    return Pair.create(false, jsonObject.getString("message"));
+                                }
+                            }
+                        } catch (Exception e) {
+                            LogUtils.e(Log.getStackTraceString(e));
+                        }
+                        return Pair.create(false, "鉴权异常,请联系管理员!");
+                    }
+
+                    @Override
+                    public void onSuccess(Pair<Boolean, String> result) {
+                        if (result.first) {
+//                            // 状态栏是否开启
+//                            boolean isStatusBarEnable = InfoDisplayApi.getStatusBarEnable();
+//                            // 导航栏是否开启
+//                            boolean isNavBarEnable = InfoDisplayApi.getNavigationBarEnable();
+//
+//                            EthernetConfig ethernetConfig = InfoNetworkApi.getEthernetConfig();
+//                            // Ip
+//                            String ip = ethernetConfig.getIpAddress();
+//                            // dhcp
+//                            int dhcp = ethernetConfig.getDhcp();
+//                            // dns1
+//                            String dns1 = ethernetConfig.getDns1Address();
+//                            // dns2
+//                            String dns2 = ethernetConfig.getDns2Address();
+//                            // mac
+//                            String mac = ethernetConfig.getMacAddress();
+//                            // route
+//                            String route = ethernetConfig.getRouteAddress();
+//                            // subnetMask
+//                            String subnetMask = ethernetConfig.getSubnetMask();
+//                            // 最优Ip地址   以太网>无线网>3、4G
+//                            String optimalIp = InfoNetworkApi.getOptimalIp();
+//                            // 设备型号
+//                            String deviceType = InfoSystemApi.getDeviceType();
+//                            // 设备序列号
+//                            String sn = InfoSystemApi.getSerialNumber();
+//                            // adb状态
+//                            int adbStatus = InfoSystemApi.getAdbStatus();
+//                            // 设备箱体温度
+//                            String temperature = InfoSystemApi.getTemperature();
+//                            // 系统编译版本号 V2.0.0 build 171226
+//                            String buildDesc = InfoSystemApi.getBuildDesc();
+//                            // 系统MCU版本号 V1.0.0 build 171226
+//                            String mcuVersion = InfoSystemApi.getMcuVersion();
+//                            // CPU使用率 当前 Cpu 的使用率,百分比,获取失败时返回”-1.00”字符串
+//                            String cpuUsageRate = InfoSystemApi.getCpuUsageRate();
+//                            // GPU使用率 当前 Gpu 的使用率,百分比,获取失败时返回”-1.00”字符串
+//                            String gpuUsageRate = InfoSystemApi.getGpuUsageRate();
+//                            // 获取内存使用情况
+//                            MemoryInfo memoryInfo = InfoSystemApi.getMemoryUsage();
+//                            // MemoryInfo .used 已使用内存大小(单位 MB)
+//                            int used = memoryInfo.used;
+//                            // MemoryInfo.total 总内存大小(MB)
+//                            int total = memoryInfo.total;
+//                            // MemoryInfo.describe 583.00MB/1.96GB
+//                            String describe = memoryInfo.describe;
+//                            int describeContents = memoryInfo.describeContents();
+//                            // 获取SDK版本 返回 SDK 版本号,获取失败返回”none”字符串
+//                            String sdkVersion = InfoSystemApi.getSdkVersion();
+//                            // 如果系统不支持该接口,则 currentLauncherName 和 preferredLauncherName 的值均为空字符串(“”)。
+//                            // 关于“默认“一词,默认 Launcher 即多个 Launcher 应用中优先级最高的,默认启动的。
+//                            LauncherInfo launcherInfo = InfoSystemApi.getLauncherName();
+//                            // 为当前生效的默认 Launcher 应用包名,默认(始终)Launcher 应用修改后该值即时更新。如果当前系统未设置默认(始终)Launcher,则该值为空字符串(“”)。
+//                            String currentLauncherName = launcherInfo.getCurrentLauncherName();
+//                            // 为持久化文件中保存的默认 Launcher 应用包名,默认 Launcher 应用修改后约 10 秒后该值才会更新,该值仅当系统内存在 2 个及以上Launcher 应用时才会存在,否则为空字符串(“”)。
+//                            String preferredLauncherName = launcherInfo.getPreferredLauncherName();
+//                            // 获取定时开关机计划
+//                            TimeSwitchConfig timeSwitchConfig = InfoTimeApi.getTimeSwitch();
+//                            // 开机时间  默认-1
+//                            long onTime = timeSwitchConfig.setOnTime;
+//                            // 关机时间 默认-1
+//                            long offTime = timeSwitchConfig.setOffTime;
+//                            // 门禁点锁状态1 true-当前锁处于上电锁门状态
+//                            //             false-当前锁处于断电开门状态或参数错误
+//                            boolean electricLock1 = InfoUtilApi.getElectricLock(1);
+//                            // 门禁点锁状态2
+//                            boolean electricLock2 = InfoUtilApi.getElectricLock(2);
+                            ThreadUtils.cancel(simpleTask);
+                            // 监听分钟广播
+                            timeTickReceiver = new TimeTickReceiver();
+                            registerReceiver(timeTickReceiver, new IntentFilter(Intent.ACTION_TIME_TICK));
+                            // 检查软件更新
+                            updateCdTimer = new CountDownTimer(20000, 1000) {
+                                @Override
+                                public void onTick(long millisUntilFinished) {
+
+                                }
+
+                                @Override
+                                public void onFinish() {
+                                    UpdateTool.INSTANCE.checkUpdate(StartActivity.this);
+                                    AsyncTask.execute(new Runnable() {
+                                        @Override
+                                        public void run() {
+                                            try {
+                                                HttpTool.INSTANCE.heartbeat();
+                                            } catch (Exception e) {
+                                                LogUtils.e(Log.getStackTraceString(e));
+                                            }
+                                        }
+                                    });
+                                    updateCdTimer.start();
+                                }
+                            };
+                            updateCdTimer.start();
+                            Tool.INSTANCE.stopApp("xn.xxp");
+                            Tool.INSTANCE.openApp("xn.xxp");
+
+                            AsyncTask.execute(() -> {
+                                boolean isSelfUpdate = SPUtils.getInstance().getBoolean("isSelfUpdate", false);
+                                if (isSelfUpdate) {
+                                    try {
+                                        String json = SPUtils.getInstance().getString("SelfUpdate", "");
+                                        UpdateTask.Task task = GsonUtils.fromJson(json, UpdateTask.Task.class);
+                                        if (null != task) {
+                                            HttpTool.INSTANCE.updateCallBack(task.getTaskId(), task.getDeviceCode(), true, true);
+                                            SPUtils.getInstance().put("isSelfUpdate", false, true);
+                                        }
+                                    } catch (IOException | JSONException e) {
+                                        LogUtils.e(Log.getStackTraceString(e));
+                                    }
+                                }
+                            });
+                            binding.tipsTV.setText("运行中");
+                        }
+                    }
+                });
+                ThreadUtils.cancel(simpleTask);
+            } else {
+                binding.tipsTV.setText("无法连接到服务器,正在重试...");
+            }
+            EthernetConfig ethernetConfig = InfoNetworkApi.getEthernetConfig();
+            String ip = ethernetConfig.getIpAddress();
+            binding.ipTV.setText((TextUtils.isEmpty(ip) ? "NULL" : ip));
+            String sn = InfoSystemApi.getSerialNumber();
+            binding.snTV.setText((TextUtils.isEmpty(sn) ? "NULL" : sn));
+        }
+    };
+
+    private void terminalAuth() {
+        ThreadUtils.executeByCachedAtFixRate(simpleTask, ThreadLocalRandom.current().nextInt(10, 20), TimeUnit.SECONDS);
+        // 清理一个月前的log
+        AsyncTask.execute(() -> {
+            List<File> fileList = FileUtils.listFilesInDir("/sdcard/logs/");
+            List<File> crashList = FileUtils.listFilesInDir("/sdcard/logs/crash");
+            for (int i = 0; i < fileList.size(); i++) {
+                File file = fileList.get(i);
+                long oneMonth = 30 * 86400 * 1000L;
+                if ((System.currentTimeMillis() - file.lastModified()) > oneMonth) {
+                    LogUtils.d("删除日志文件", file);
+                    FileUtils.delete(file);
+                }
+            }
+            for (int i = 0; i < crashList.size(); i++) {
+                File file = crashList.get(i);
+                long oneMonth = 30 * 86400 * 1000L;
+                if ((System.currentTimeMillis() - file.lastModified()) > oneMonth) {
+                    LogUtils.d("删除crash文件", file);
+                    FileUtils.delete(file);
+                }
+            }
+        });
+    }
+
+    private InputConfirmPopupView inputConfirmPopupView;
+
+    @Override
+    protected void onPostCreate(@Nullable Bundle savedInstanceState) {
+        super.onPostCreate(savedInstanceState);
+        binding.versionNameTV.setText(AppUtils.getAppVersionName());
+
+        inputConfirmPopupView = new XPopup.Builder(this).autoDismiss(false).asInputConfirm("Tips", "请输入管理员密码",
+                text -> {
+                    if (text.equals(SPUtils.getInstance().getString(AppConstant.ADMIN_PASSWORD, "admin@098&"))) {
+                        ActivityUtils.startActivity(SettingActivity.class);
+                        inputConfirmPopupView.dismiss();
+                    } else {
+                        ToastUtils.showLong("密码不正确,请重新输入!");
+                    }
+                });
+        binding.logoIV.setOnLongClickListener(v -> {
+            if (AppUtils.isAppDebug()) {
+                ActivityUtils.startActivity(SettingActivity.class);
+            } else {
+                EditText editText = inputConfirmPopupView.getEditText();
+                if (null != editText) {
+                    editText.setText("");
+                }
+                inputConfirmPopupView.show();
+            }
+            return true;
+        });
+    }
+
+    @Override
+    protected void onPostResume() {
+        super.onPostResume();
+    }
+
+    @Override
+    protected void onDestroy() {
+        super.onDestroy();
+        if (null != timeTickReceiver) {
+            unregisterReceiver(timeTickReceiver);
+            timeTickReceiver = null;
+        }
+        if (null != inputConfirmPopupView) {
+            inputConfirmPopupView.destroy();
+        }
+    }
+
+    public PingResult parsePingOutput(String pingOutput) {
+        PingResult result = new PingResult();
+        // 解析目标IP
+        Pattern ipPattern = Pattern.compile("PING (\\d+\\.\\d+\\.\\d+\\.\\d+)");
+        Matcher ipMatcher = ipPattern.matcher(pingOutput);
+        if (ipMatcher.find()) {
+            result.setTargetIP(ipMatcher.group(1));
+        }
+        // 解析统计信息
+        Pattern statsPattern = Pattern.compile(
+                "(\\d+) packets transmitted, (\\d+) received, (\\d+)% packet loss, time (\\d+)ms"
+        );
+        Matcher statsMatcher = statsPattern.matcher(pingOutput);
+        if (statsMatcher.find()) {
+            result.setPacketsTransmitted(Integer.parseInt(statsMatcher.group(1)));
+            result.setPacketsReceived(Integer.parseInt(statsMatcher.group(2)));
+            result.setPacketLossPercentage(Double.parseDouble(statsMatcher.group(3)));
+        }
+        // 解析RTT信息
+        Pattern rttPattern = Pattern.compile(
+                "rtt min/avg/max/mdev = (\\d+\\.\\d+)/(\\d+\\.\\d+)/(\\d+\\.\\d+)/(\\d+\\.\\d+) ms"
+        );
+        Matcher rttMatcher = rttPattern.matcher(pingOutput);
+        if (rttMatcher.find()) {
+            result.setMinRtt(Double.parseDouble(rttMatcher.group(1)));
+            result.setAvgRtt(Double.parseDouble(rttMatcher.group(2)));
+            result.setMaxRtt(Double.parseDouble(rttMatcher.group(3)));
+            result.setMdevRtt(Double.parseDouble(rttMatcher.group(4)));
+        }
+        return result;
+    }
 }

+ 7 - 3
app/src/main/java/xn/update/Tool.java

@@ -3,11 +3,14 @@ package xn.update;
 import android.net.Uri;
 import android.util.Log;
 
+import androidx.annotation.WorkerThread;
+
 import com.blankj.utilcode.util.AppUtils;
 import com.blankj.utilcode.util.LogUtils;
 import com.blankj.utilcode.util.SPUtils;
 import com.blankj.utilcode.util.ShellUtils;
 import com.hikvision.dmb.system.InfoSystemApi;
+import com.hikvision.dmb.util.InfoUtilApi;
 
 import okhttp3.HttpUrl;
 import xn.update.constant.AppConstant;
@@ -15,6 +18,10 @@ import xn.update.constant.AppConstant;
 public enum Tool {
     INSTANCE;
 
+    Tool() {
+        InfoUtilApi.getRoot();
+    }
+
     public void reStartApp() {
         exitApp();
         InfoSystemApi.execCommand("monkey -p " + AppUtils.getAppPackageName() + " -c android.intent.category.LAUNCHER 1");
@@ -49,9 +56,6 @@ public enum Tool {
         return ShellUtils.execCmd("am force-stop " + packageName, true);
     }
 
-    public void startTaskService() {
-        cmd("am startservice -n xn.update/xn.update.service.TaskService");
-    }
 
     public int cmd(String cmd) {
         int result = InfoSystemApi.execCommand(cmd);

+ 184 - 1
app/src/main/java/xn/update/UpdateTool.java

@@ -1,4 +1,187 @@
 package xn.update;
 
-public class UpdateTool {
+import android.annotation.SuppressLint;
+import android.app.DownloadManager;
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.content.pm.ApplicationInfo;
+import android.content.pm.PackageInfo;
+import android.content.pm.PackageManager;
+import android.net.Uri;
+import android.os.AsyncTask;
+import android.os.Environment;
+import android.util.Log;
+
+import com.blankj.utilcode.util.AppUtils;
+import com.blankj.utilcode.util.FileUtils;
+import com.blankj.utilcode.util.GsonUtils;
+import com.blankj.utilcode.util.LogUtils;
+import com.blankj.utilcode.util.SPUtils;
+import com.blankj.utilcode.util.ThreadUtils;
+
+import org.json.JSONException;
+
+import java.io.File;
+import java.io.IOException;
+import java.util.List;
+import java.util.concurrent.atomic.AtomicBoolean;
+
+import okhttp3.Response;
+import xn.update.constant.AppConstant;
+import xn.update.http.HttpTool;
+import xn.update.http.bean.response.UpdateTask;
+
+public enum UpdateTool {
+    INSTANCE;
+
+    private UpdateTask.Task task;
+    @SuppressLint("SdCardPath")
+    private final String apkPath = "/sdcard/Download/apk/";
+    private final String apkName = "app.apk";
+    private DownloadManager downloadManager;
+    private long downloadId;
+    private long lastRunTime;
+
+    private final AtomicBoolean isUploading = new AtomicBoolean(false);
+
+    public void checkUpdate(Context context) {
+        // 若是3分钟都没下载完成,则认为下载失败
+        long runTime = (System.currentTimeMillis() - lastRunTime);
+        if (lastRunTime > 0 && runTime > 1000 * 60 * 3) {
+            isUploading.set(false);
+        }
+        // 空闲中
+        if (!isUploading.get() && SPUtils.getInstance().getBoolean(AppConstant.AUTO_UPDATE, true)) {
+            lastRunTime = System.currentTimeMillis();
+            isUploading.set(true);
+            if (null == downloadManager) {
+                downloadManager = (DownloadManager) context.getApplicationContext().getSystemService(Context.DOWNLOAD_SERVICE);
+            }
+            ThreadUtils.executeByCached(new ThreadUtils.SimpleTask<Boolean>() {
+                @SuppressLint({"UnspecifiedRegisterReceiverFlag", "SdCardPath"})
+                @Override
+                public Boolean doInBackground() throws Throwable {
+                    try {
+                        Response response = HttpTool.INSTANCE.update();
+                        if (response.isSuccessful()) {
+                            String json = response.body().string();
+                            UpdateTask updateTask = GsonUtils.fromJson(json, UpdateTask.class);
+                            if (updateTask.getCode() == 200) {
+                                List<UpdateTask.Task> updateTaskData = updateTask.getData();
+                                if (null != updateTaskData && !updateTaskData.isEmpty()) {
+                                    task = updateTaskData.get(0);
+                                    FileUtils.createOrExistsDir(apkPath);
+                                    FileUtils.deleteAllInDir(apkPath);
+                                    if (!AppUtils.getAppPackageName().equals(task.getStartLaunchPackage())) {
+                                        SPUtils.getInstance().put(AppConstant.KeySP.MASTER_PACKAGE_NAME, task.getStartLaunchPackage());
+                                    }
+                                    DownloadManager.Request request = new DownloadManager.Request(Uri.parse(Tool.INSTANCE.checkUrl(task.getApkUrl())));
+                                    request.setDestinationInExternalPublicDir(Environment.DIRECTORY_DOWNLOADS, "apk/" + apkName);
+                                    request.setMimeType("application/vnd.android.package-archive");
+                                    request.setVisibleInDownloadsUi(false);
+                                    request.setNotificationVisibility(DownloadManager.Request.VISIBILITY_HIDDEN);
+                                    context.registerReceiver(downloadApkReceiver, new IntentFilter(DownloadManager.ACTION_DOWNLOAD_COMPLETE));
+                                    downloadId = downloadManager.enqueue(request);
+                                    LogUtils.d("发起下载", downloadId);
+                                    return true;
+                                }
+                            } else {
+                                LogUtils.e(updateTask.getMessage());
+                            }
+                        }
+                    } catch (Exception e) {
+                        LogUtils.e(Log.getStackTraceString(e));
+                    }
+                    return false;
+                }
+
+                @Override
+                public void onSuccess(Boolean result) {
+                    isUploading.set(result);
+                }
+            });
+        } else {
+            LogUtils.d("正在升级中...");
+        }
+    }
+
+    private final BroadcastReceiver downloadApkReceiver = new BroadcastReceiver() {
+        @Override
+        public void onReceive(Context context, Intent intent) {
+            long id = intent.getLongExtra(DownloadManager.EXTRA_DOWNLOAD_ID, -1);
+            LogUtils.d("下载成功", downloadId, task);
+            if (downloadId == id) {
+                if (null != task) {
+                    AsyncTask.execute(() -> {
+                        try {
+                            HttpTool.INSTANCE.updateCallBack(task.getTaskId(), task.getDeviceCode(), true, false);
+                        } catch (IOException | JSONException e) {
+                            LogUtils.e(Log.getStackTraceString(e));
+                        }
+                        // 升级其它App
+                        if (!task.getStartLaunchPackage().equals(AppUtils.getAppPackageName())) {
+                            Tool.INSTANCE.cmd("pm uninstall" + task.getStartLaunchPackage());
+                        } else {
+                            SPUtils.getInstance().put("isSelfUpdate", true, true);
+                            SPUtils.getInstance().put("SelfUpdate", GsonUtils.toJson(task), true);
+                        }
+                        Tool.INSTANCE.cmd("pm install -d -g " + apkPath + apkName);
+                        Tool.INSTANCE.openApp(task.getStartLaunchPackage());
+                        try {
+                            HttpTool.INSTANCE.updateCallBack(task.getTaskId(), task.getDeviceCode(), true, true);
+                        } catch (IOException | JSONException e) {
+                            LogUtils.e(Log.getStackTraceString(e));
+                        }
+                        isUploading.set(false);
+                    });
+                }
+            }
+        }
+    };
+
+    /**
+     * 解析未安装的APK文件
+     *
+     * @param context 上下文
+     * @param apkPath APK文件路径
+     */
+    public void parseApkFile(Context context, String apkPath) {
+        PackageManager pm = context.getPackageManager();
+        PackageInfo packageInfo = pm.getPackageArchiveInfo(apkPath,
+                PackageManager.GET_PERMISSIONS | PackageManager.GET_META_DATA);
+
+        if (packageInfo != null) {
+            // 获取应用信息
+            ApplicationInfo appInfo = packageInfo.applicationInfo;
+
+            // 修复资源路径问题
+            appInfo.sourceDir = apkPath;
+            appInfo.publicSourceDir = apkPath;
+
+            // 获取基本信息
+            String packageName = packageInfo.packageName;
+            String versionName = packageInfo.versionName;
+            int versionCode = packageInfo.versionCode;
+
+            // 获取权限
+            String[] permissions = packageInfo.requestedPermissions;
+
+            // 获取APK大小
+            File apkFile = new File(apkPath);
+            long apkSize = apkFile.length();
+
+            // 打印信息
+            Log.d("APK-INFO", "包名: " + packageName);
+            Log.d("APK-INFO", "版本: " + versionName + "(" + versionCode + ")");
+            Log.d("APK-INFO", "大小: " + (apkSize / 1024) + "KB");
+
+            if (permissions != null) {
+                for (String perm : permissions) {
+                    Log.d("APK-INFO", "权限: " + perm);
+                }
+            }
+        }
+    }
 }

+ 7 - 12
app/src/main/java/xn/update/app/App.java

@@ -11,8 +11,10 @@ import com.blankj.utilcode.util.FileUtils;
 import com.blankj.utilcode.util.LogUtils;
 import com.blankj.utilcode.util.SPUtils;
 import com.blankj.utilcode.util.ThreadUtils;
+import com.blankj.utilcode.util.ToastUtils;
 import com.github.anrwatchdog.ANRError;
 import com.github.anrwatchdog.ANRWatchDog;
+import com.hikvision.dmb.util.InfoUtilApi;
 
 import xn.update.Tool;
 
@@ -24,21 +26,14 @@ public class App extends Application {
         new ANRWatchDog(5000).setANRListener(new ANRWatchDog.ANRListener() {
             @Override
             public void onAppNotResponding(ANRError error) {
-                ThreadUtils.runOnUiThread(new Runnable() {
+                LogUtils.e("ANR", error.getMessage(), error);
+                ToastUtils.showLong("检测到应用卡顿,设备即将重启恢复!");
+                ThreadUtils.runOnUiThreadDelayed(new Runnable() {
                     @Override
                     public void run() {
-                        Toast.makeText(ActivityUtils.getTopActivity(), "检测到应用卡顿,重启应用恢复!", Toast.LENGTH_LONG).show();
-                        int anrSize = SPUtils.getInstance().getInt("ANR_size", 0);
-                        if (anrSize > 5) {
-                            SPUtils.getInstance().put("ANR_size", 0);
-                            Tool.INSTANCE.reboot();
-                        } else {
-                            SPUtils.getInstance().put("ANR_size", ++anrSize);
-                            Tool.INSTANCE.reStartApp();
-                        }
+                        Tool.INSTANCE.reboot();
                     }
-                });
-                LogUtils.e(error.getMessage(), error);
+                }, 3000);
             }
         }).start();
         String logPath = "/sdcard/logs/";

+ 38 - 1
app/src/main/java/xn/update/http/HttpTool.java

@@ -1,10 +1,19 @@
 package xn.update.http;
 
 
+import com.blankj.utilcode.util.AppUtils;
+import com.blankj.utilcode.util.ShellUtils;
+import com.hikvision.dmb.EthernetConfig;
+import com.hikvision.dmb.network.InfoNetworkApi;
+import com.hikvision.dmb.system.InfoSystemApi;
+
 import org.json.JSONException;
 import org.json.JSONObject;
 
+import java.io.BufferedReader;
+import java.io.ByteArrayInputStream;
 import java.io.IOException;
+import java.io.InputStreamReader;
 
 import okhttp3.Response;
 import xn.update.Tool;
@@ -51,7 +60,35 @@ public enum HttpTool {
 
     public Response heartbeat() throws JSONException, IOException {
         JSONObject jsonObject = new JSONObject();
-        return OkHttpUtils.INSTANCE.postSync(Tool.INSTANCE.getBaseUrl() + "terminal/sys/config/info/getConfigByType", jsonObject.toString());
+        ShellUtils.CommandResult commandResult = ShellUtils.execCmd("dumpsys activity activities", true);
+        BufferedReader reader = new BufferedReader(new InputStreamReader(new ByteArrayInputStream(commandResult.successMsg.getBytes())));
+        String line;
+        boolean isShow = false;
+        while ((line = reader.readLine()) != null) {
+            if (line.contains("mResumedActivity") || line.contains("mCurrentFocus")) {
+                String[] itemArray = line.split(" ", -1);
+                for (String item : itemArray) {
+                    if (item.contains("/")) {
+                        String packageName = item.substring(0, item.indexOf("/"));
+                        isShow = AppUtils.getAppPackageName().equals(packageName);
+                        break;
+                    }
+                }
+                break;
+            }
+        }
+
+        boolean adbEnabled = (1 == InfoSystemApi.getAdbStatus());
+        EthernetConfig ethernetConfig = InfoNetworkApi.getEthernetConfig();
+        // Ip
+        String ip = ethernetConfig.getIpAddress();
+        jsonObject.put("deviceNo", "EBOARD_" + InfoSystemApi.getSerialNumber());
+        jsonObject.put("versionName", AppUtils.getAppVersionName());
+        jsonObject.put("packageName", AppUtils.getAppPackageName());
+        jsonObject.put("isShow", isShow);
+        jsonObject.put("adbEnabled", adbEnabled);
+        jsonObject.put("ip", ip);
+        return OkHttpUtils.INSTANCE.postSync(Tool.INSTANCE.getBaseUrl() + "terminal/machine/upgrade/heartbeat", jsonObject.toString());
     }
 
 }

+ 0 - 1
app/src/main/java/xn/update/http/bean/AllLogInterceptor.java

@@ -33,7 +33,6 @@ public class AllLogInterceptor implements Interceptor {
                 newRequestBuilder.header("TerminalAuth", "Bearer " + terminalAuth);
             }
             newRequestBuilder.header("SN", Tool.INSTANCE.getSerialNumber());
-            newRequestBuilder.header("IP", SPUtils.getInstance().getString("IP", "127.0.0.1"));
             request = newRequestBuilder.build();
             long t1 = System.nanoTime();
             // 记录请求信息

+ 1 - 0
app/src/main/java/xn/update/http/bean/response/UpdateTask.java

@@ -54,6 +54,7 @@ public class UpdateTask {
         private String deviceCode;
         private String apkUrl;
         private String versionName;
+        // Apk包名
         private String startLaunchPackage;
         private boolean isForceUpdate;
 //        private String appPackageName = "xn.xxp";

+ 15 - 1
app/src/main/java/xn/update/receiver/BootReceiver.java

@@ -1,4 +1,18 @@
 package xn.update.receiver;
 
-public class ootReceiver {
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+
+import com.blankj.utilcode.util.ActivityUtils;
+
+import xn.update.StartActivity;
+
+public class BootReceiver extends BroadcastReceiver {
+    @Override
+    public void onReceive(Context context, Intent intent) {
+        if (Intent.ACTION_BOOT_COMPLETED.equals(intent.getAction())) {
+            ActivityUtils.startActivity(StartActivity.class);
+        }
+    }
 }

+ 1 - 149
app/src/main/java/xn/update/receiver/TimeTickReceiver.java

@@ -1,178 +1,30 @@
 package xn.update.receiver;
 
-import android.annotation.SuppressLint;
-import android.app.DownloadManager;
 import android.content.BroadcastReceiver;
 import android.content.Context;
 import android.content.Intent;
-import android.content.IntentFilter;
-import android.net.Uri;
-import android.os.AsyncTask;
-import android.os.Environment;
 import android.util.Log;
 
-import com.blankj.utilcode.util.ActivityUtils;
-import com.blankj.utilcode.util.AppUtils;
-import com.blankj.utilcode.util.FileUtils;
-import com.blankj.utilcode.util.GsonUtils;
 import com.blankj.utilcode.util.LogUtils;
-import com.blankj.utilcode.util.SPUtils;
 import com.blankj.utilcode.util.ShellUtils;
 import com.blankj.utilcode.util.ThreadUtils;
-import com.blankj.utilcode.util.ToastUtils;
-import com.blankj.utilcode.util.Utils;
-
-import org.json.JSONException;
 
 import java.io.BufferedReader;
 import java.io.ByteArrayInputStream;
-import java.io.IOException;
 import java.io.InputStreamReader;
-import java.util.List;
 
-import okhttp3.Response;
 import xn.update.Tool;
-import xn.update.constant.AppConstant;
-import xn.update.http.HttpTool;
-import xn.update.http.bean.response.UpdateTask;
 
 public class TimeTickReceiver extends BroadcastReceiver {
-    private volatile boolean isRunning = false;
-    private long lastRunTime;
-    private DownloadManager downloadManager;
-    private BroadcastReceiver downloadApkReceiver;
-    private long downloadId;
-    @SuppressLint("SdCardPath")
-    private final String apkPath = "/sdcard/Download/apk/";
-    private final String apkName = "app.apk";
-    private UpdateTask.Task task;
+
 
     @Override
     public void onReceive(Context context, Intent intent) {
-        // 保活用
-        Tool.INSTANCE.startTaskService();
         Intent newIntent = new Intent("XN_ACTION");
         intent.putExtra("heartbeat", "heartbeat");
         context.sendBroadcast(newIntent);
-        // 若是3分钟都没下载完成,则认为下载失败
-        long runTime = (System.currentTimeMillis() - lastRunTime);
-        if (lastRunTime > 0 && runTime > 1000 * 60 * 3) {
-            isRunning = false;
-        }
-        // 空闲中
-        if (!isRunning && SPUtils.getInstance().getBoolean(AppConstant.AUTO_UPDATE, true)) {
-            lastRunTime = System.currentTimeMillis();
-            isRunning = true;
-            if (null == downloadManager) {
-                downloadManager = (DownloadManager) context.getApplicationContext().getSystemService(Context.DOWNLOAD_SERVICE);
-                downloadApkReceiver = new BroadcastReceiver() {
-                    @Override
-                    public void onReceive(Context context, Intent intent) {
-                        long id = intent.getLongExtra(DownloadManager.EXTRA_DOWNLOAD_ID, -1);
-                        LogUtils.d("下载成功", downloadId, task);
-                        if (downloadId == id) {
-                            if (null != task) {
-                                AsyncTask.execute(() -> {
-                                    try {
-                                        HttpTool.INSTANCE.updateCallBack(task.getTaskId(), task.getDeviceCode(), true, false);
-                                    } catch (IOException | JSONException e) {
-                                        LogUtils.e(Log.getStackTraceString(e));
-                                    }
-                                });
-                                // 当前App自升级
-                                if (task.getStartLaunchPackage().equals(AppUtils.getAppPackageName())) {
-                                    AsyncTask.execute(() -> {
-                                        try {
-                                            HttpTool.INSTANCE.updateCallBack(task.getTaskId(), task.getDeviceCode(), true, true);
-                                            if (AppUtils.getAppPackageName().equals("xn.xxp")) {
-                                                Tool.INSTANCE.cmd("pm install -d -g " + apkPath + apkName);
-                                                Tool.INSTANCE.openApp(task.getStartLaunchPackage());
-                                            } else {
-                                                ShellUtils.execCmdAsync("pm uninstall " + "xn.xxp", true, new Utils.Consumer<ShellUtils.CommandResult>() {
-                                                    @Override
-                                                    public void accept(ShellUtils.CommandResult commandResult) {
-                                                        LogUtils.d(commandResult);
-                                                        Tool.INSTANCE.cmd("pm install -d -g " + apkPath + apkName);
-                                                        Tool.INSTANCE.openApp(task.getStartLaunchPackage());
-                                                    }
-                                                });
-                                            }
-                                        } catch (IOException | JSONException e) {
-                                            LogUtils.e(Log.getStackTraceString(e));
-                                        }
-                                    });
-                                }
-                                // 升级其它App
-                                else {
-                                    ShellUtils.CommandResult commandResult = ShellUtils.execCmd("pm install -d -g " + apkPath + apkName, true);
-                                    LogUtils.d("安装app", commandResult);
-                                    if (commandResult.result == 0) {
-                                        AsyncTask.execute(() -> {
-                                            try {
-                                                HttpTool.INSTANCE.updateCallBack(task.getTaskId(), task.getDeviceCode(), true, true);
-                                                Tool.INSTANCE.startTaskService();
-                                            } catch (IOException | JSONException e) {
-                                                LogUtils.e(Log.getStackTraceString(e));
-                                            }
-                                        });
-                                        Tool.INSTANCE.openApp("xn.xxp");
-                                    } else {
-                                        LogUtils.e(commandResult.errorMsg);
-                                    }
-                                }
-                            }
-                            isRunning = false;
-                        }
-                    }
-                };
-            }
-            ThreadUtils.executeByCached(new ThreadUtils.SimpleTask<Boolean>() {
-                @SuppressLint({"UnspecifiedRegisterReceiverFlag", "SdCardPath"})
-                @Override
-                public Boolean doInBackground() throws Throwable {
-                    try {
-                        Response response = HttpTool.INSTANCE.update();
-                        if (response.isSuccessful()) {
-                            String json = response.body().string();
-                            UpdateTask updateTask = GsonUtils.fromJson(json, UpdateTask.class);
-                            if (updateTask.getCode() == 200) {
-                                List<UpdateTask.Task> updateTaskData = updateTask.getData();
-                                if (null != updateTaskData && !updateTaskData.isEmpty()) {
-                                    task = updateTaskData.get(0);
-                                    FileUtils.createOrExistsDir(apkPath);
-                                    FileUtils.deleteAllInDir(apkPath);
-                                    if (!AppUtils.getAppPackageName().equals(task.getStartLaunchPackage())) {
-                                        SPUtils.getInstance().put(AppConstant.KeySP.MASTER_PACKAGE_NAME, task.getStartLaunchPackage());
-                                    }
-                                    DownloadManager.Request request = new DownloadManager.Request(Uri.parse(Tool.INSTANCE.checkUrl(task.getApkUrl())));
-                                    request.setDestinationInExternalPublicDir(Environment.DIRECTORY_DOWNLOADS, "apk/" + apkName);
-                                    request.setMimeType("application/vnd.android.package-archive");
-                                    request.setVisibleInDownloadsUi(false);
-                                    request.setNotificationVisibility(DownloadManager.Request.VISIBILITY_HIDDEN);
-                                    context.registerReceiver(downloadApkReceiver, new IntentFilter(DownloadManager.ACTION_DOWNLOAD_COMPLETE));
-                                    downloadId = downloadManager.enqueue(request);
-                                    LogUtils.d("发起下载", downloadId);
-                                    return true;
-                                }
-                            } else {
-                                LogUtils.e(updateTask.getMessage());
-                            }
-                        }
-                    } catch (Exception e) {
-                        LogUtils.e(Log.getStackTraceString(e));
-                    }
-                    return false;
-                }
-
-                @Override
-                public void onSuccess(Boolean result) {
-                    isRunning = result;
-                }
-            });
-        }
         // 拉起主应用
         startMasterApp();
-
     }
 
     private void startMasterApp() {

+ 40 - 7
app/src/main/res/layout/activity_main.xml

@@ -4,8 +4,13 @@
     xmlns:tools="http://schemas.android.com/tools"
     android:id="@+id/main"
     android:layout_width="match_parent"
-    android:layout_height="match_parent"
-    tools:context=".MainActivity">
+    android:layout_height="match_parent">
+
+    <TextView
+        android:id="@+id/time"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:layout_alignParentEnd="true" />
 
     <ImageView
         android:id="@+id/logo_IV"
@@ -14,20 +19,48 @@
         android:layout_centerInParent="true"
         android:src="@mipmap/logo" />
 
-    <TextView
-        android:id="@+id/tips_TV"
+    <LinearLayout
         android:layout_width="wrap_content"
         android:layout_height="wrap_content"
         android:layout_below="@id/logo_IV"
         android:layout_centerHorizontal="true"
         android:layout_marginTop="20dp"
-        android:text="未初始化"
-        android:textColor="@color/black" />
+        android:gravity="center">
+
+        <ProgressBar
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content" />
+
+        <TextView
+            android:id="@+id/tips_TV"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:layout_marginStart="10dp"
+            android:text="10s后开始初始化"
+            android:textColor="@color/black" />
+
+    </LinearLayout>
+
 
     <TextView
         android:id="@+id/version_name_TV"
         android:layout_width="wrap_content"
         android:layout_height="wrap_content"
-        android:layout_alignParentBottom="true" />
+        android:layout_alignParentBottom="true"
+        android:text="1.0" />
+
+    <TextView
+        android:id="@+id/ip_TV"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:layout_above="@id/version_name_TV"
+        android:text="127.0.0.1" />
+
+    <TextView
+        android:id="@+id/sn_TV"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:layout_above="@id/ip_TV"
+        android:text="Dev" />
 
 </RelativeLayout>

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

@@ -4,8 +4,7 @@
     xmlns:tools="http://schemas.android.com/tools"
     android:id="@+id/main"
     android:layout_width="match_parent"
-    android:layout_height="match_parent"
-    tools:context=".MainActivity">
+    android:layout_height="match_parent">
 
     <ImageView
         android:layout_width="wrap_content"