[FEAT]:新增遠端維護鎖定模式與隱藏後門機制

1. 實作遠端維護鎖定功能,支援 MQTT lock 與 unlock 指令。
2. 新增 MaintenanceActivity 頁面,顯示「機器維護中」背景圖並封鎖所有操作。
3. 在維護頁面實作隱藏後門機制(長按畫面中央 5 秒觸發管理員登入視窗)。
4. 更新 SettingsDao 使用 SQLite 持久化機台鎖定狀態,確保重啟後依然生效。
5. 更新 HomeActivity 入口邏輯,在開機啟動時優先檢查鎖定狀態。
6. 修正維護頁面資源路徑,並清理干擾編譯的 NTFS Zone Identifier 檔案。
This commit is contained in:
sky121113 2026-05-07 17:29:01 +08:00
parent d722b3273b
commit 18943e8a0f
11 changed files with 391 additions and 125 deletions

View File

@ -80,6 +80,11 @@
android:name=".ui.RestartActivity"
android:theme="@style/Theme.ImageBackground"
android:exported="false" />
<activity
android:name=".ui.MaintenanceActivity"
android:screenOrientation="portrait"
android:theme="@style/Theme.SmartDevice"
android:exported="false" />
<activity
android:name=".HomeActivity"
android:exported="true">

View File

@ -196,6 +196,19 @@ public class HomeActivity extends AppCompatActivityAbstract {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
// 啟動鎖定檢查
com.unibuy.smartdevice.database.SettingsDao settingsDao = new com.unibuy.smartdevice.database.SettingsDao(getApplicationContext());
if (settingsDao.isMachineLocked()) {
settingsDao.close();
Intent intent = new Intent(this, com.unibuy.smartdevice.ui.MaintenanceActivity.class);
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TOP);
startActivity(intent);
finish();
return;
}
settingsDao.close();
binding = ActivityHomeBinding.inflate(getLayoutInflater());
setContentView(binding.getRoot());

View File

@ -10,9 +10,11 @@ import android.content.Intent;
import android.content.pm.PackageInfo;
import android.content.pm.PackageManager;
import android.os.Bundle;
import android.os.Handler;
import android.os.Process;
import android.text.InputType;
import android.view.KeyEvent;
import android.view.MotionEvent;
import android.view.View;
import android.view.WindowManager;
import android.widget.EditText;
@ -92,7 +94,7 @@ public class MainActivity extends AppCompatActivityAbstract {
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
// 讀取最後設定語言的設定
// LanguageHelper.applySavedLanguage(this);
// LanguageHelper.applySavedLanguage(this);
binding = ActivityMainBinding.inflate(getLayoutInflater());
setContentView(binding.getRoot());
@ -100,13 +102,13 @@ public class MainActivity extends AppCompatActivityAbstract {
httpAPI = new HttpAPI(getHandlerMain());
starCloudAPI = new StarCloudAPI(getHandlerMain(), getHandlerMain());
//---- 環境尺寸設定 ----
// ---- 環境尺寸設定 ----
ViewCompat.setOnApplyWindowInsetsListener(binding.main, (v, insets) -> {
Insets systemBars = insets.getInsets(WindowInsetsCompat.Type.systemBars());
v.setPadding(systemBars.left, systemBars.top, systemBars.right, systemBars.bottom);
return insets;
});
//---- 環境尺寸設定 ----
// ---- 環境尺寸設定 ----
initialData();
@ -184,7 +186,6 @@ public class MainActivity extends AppCompatActivityAbstract {
pendingAction = null;
});
binding.buttonGotoShoppingPlatform.setOnClickListener(v -> {
if (MyApp.getInstance().getMachine().getMachineID().isEmpty()) {
getHandlerMain().start("MainActivity", "@先綁定機器");
@ -194,14 +195,32 @@ public class MainActivity extends AppCompatActivityAbstract {
boolean hidden = isNavigationReallyHidden();
if (hidden) {
MyApp.getInstance().setSuAccess("");
getTools().gotoActivity(FontendActivity.class);
finish();
enterShoppingPlatform();
} else {
showMustHideSystemBarsDialog();
}
});
// 🛠 開發者後門長按 3 秒強制進入購物平台 (適合手機開發測試)
final Handler longPressHandler = new Handler();
final Runnable longPressRunnable = () -> {
Toast.makeText(getCtx(), "🛠️ 開發者模式:強制進入購物平台", Toast.LENGTH_SHORT).show();
enterShoppingPlatform();
};
binding.buttonGotoShoppingPlatform.setOnTouchListener((v, event) -> {
switch (event.getAction()) {
case android.view.MotionEvent.ACTION_DOWN:
longPressHandler.postDelayed(longPressRunnable, 2000); // 2秒
break;
case android.view.MotionEvent.ACTION_UP:
case android.view.MotionEvent.ACTION_CANCEL:
longPressHandler.removeCallbacks(longPressRunnable);
break;
}
return false; // 回傳 false 以確保 setOnClickListener 仍能接收到點擊事件
});
binding.buttonGotoSettingTest.setOnClickListener(v -> getTools().gotoActivity(SettingTestActivity.class));
binding.buttonGotoDebug.setOnClickListener(v -> getTools().gotoActivity(DebugActivity.class));
@ -212,7 +231,8 @@ public class MainActivity extends AppCompatActivityAbstract {
binding.buttonQrCodeTest.setOnClickListener(new ButtonQrCodeTestOnClick(getHandlerMain()));
// binding.buttonOtherTest.setOnClickListener(new ButtonOtherTextOnClick(getHandlerMain()));
// binding.buttonOtherTest.setOnClickListener(new
// ButtonOtherTextOnClick(getHandlerMain()));
binding.buttonGotoXyshj.setOnClickListener(new ButtonGotoXyshjOnClick(getHandlerMain()));
@ -220,12 +240,11 @@ public class MainActivity extends AppCompatActivityAbstract {
VideoImageMixedPlayer.clearAdsCache(this);
});
//鈔票機設定
// 鈔票機設定
binding.buttonGotoCash.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
// 建立 Intent從目前 Activity 跳轉到 CashMachineSettings
Intent intent = new Intent(MainActivity.this, CashMachineSettings.class);
startActivity(intent);
@ -258,9 +277,6 @@ public class MainActivity extends AppCompatActivityAbstract {
});
}
private static class ButtonQrCodeTestOnClick extends HandlerMainSchedulerOnClick {
public ButtonQrCodeTestOnClick(HandlerMain handlerMain) {
super(handlerMain);
@ -279,7 +295,7 @@ public class MainActivity extends AppCompatActivityAbstract {
handlerMain.start(getClass().getSimpleName(), "@40秒內刷條碼");
byte[] buffer = new byte[1000];
usbDev.read(buffer, 100);
for (int i=0; i<7; i++) {
for (int i = 0; i < 7; i++) {
if (isRun() && openBarcode) {
int size = usbDev.read(buffer, 5000);
if (size > 5) {
@ -287,7 +303,7 @@ public class MainActivity extends AppCompatActivityAbstract {
int sorLength = barCodeString.length();
barCodeString = barCodeString.trim();
int newLength = barCodeString.length();
getLogs().info(barCodeString +" 原碼長:"+sorLength + " 新碼長:" + newLength);
getLogs().info(barCodeString + " 原碼長:" + sorLength + " 新碼長:" + newLength);
handlerMain.start(getClass().getSimpleName(), getLogs().getMessage());
getLogs().clearMessage();
openBarcode = false;
@ -296,7 +312,7 @@ public class MainActivity extends AppCompatActivityAbstract {
}
usbDev.closePort();
} catch (LogsUnsupportedOperationException | LogsIOException | LogsEmptyException e) {
// getLogs().warning(e);
// getLogs().warning(e);
getLogs().warning(e);
}
}
@ -317,7 +333,6 @@ public class MainActivity extends AppCompatActivityAbstract {
}
}
public static class ButtonNfcResetOnClick extends HandlerMainSchedulerOnClick {
public ButtonNfcResetOnClick(HandlerMain handlerMain) {
@ -392,17 +407,19 @@ public class MainActivity extends AppCompatActivityAbstract {
handlerMain.start(getSrcHandlerMain().getLogs().getClassName(), "請先安裝興元管理程式");
}
// List<PackageInfo> packages = MainActivity.this.getPackageManager().getInstalledPackages(0);
// for (PackageInfo packageInfo : packages) {
// if ("com.xyshj.machine".equals(packageInfo.packageName)) {
// Intent it = new Intent("android.intent.action.xinyuan.setting");
// it.putExtra("showLoginDialog", true);
// MainActivity.this.startActivity(it);
// android.os.Process.killProcess(Process.myPid());
// return;
// }
// }
// handlerMain.start(getSrcHandlerMain().getLogs().getClassName(), "請先安裝興元管理程式");
// List<PackageInfo> packages =
// MainActivity.this.getPackageManager().getInstalledPackages(0);
// for (PackageInfo packageInfo : packages) {
// if ("com.xyshj.machine".equals(packageInfo.packageName)) {
// Intent it = new Intent("android.intent.action.xinyuan.setting");
// it.putExtra("showLoginDialog", true);
// MainActivity.this.startActivity(it);
// android.os.Process.killProcess(Process.myPid());
// return;
// }
// }
// handlerMain.start(getSrcHandlerMain().getLogs().getClassName(),
// "請先安裝興元管理程式");
}
@Override
@ -473,7 +490,8 @@ public class MainActivity extends AppCompatActivityAbstract {
public boolean isNavigationReallyHidden() {
WindowInsetsCompat insets = ViewCompat.getRootWindowInsets(binding.main);
if (insets == null) return false;
if (insets == null)
return false;
if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.Q) {
// Android 10+ ( 12): 使用 SystemGestures 判斷
@ -552,12 +570,13 @@ public class MainActivity extends AppCompatActivityAbstract {
}
}).create();
// 🛠 Debug Backdoor: 長按對話框標題跳過登入
// 🛠 Debug Backdoor: 長按對話框標題 3 跳過登入
dialog.setOnShowListener(d -> {
int titleId = context.getResources().getIdentifier("alertTitle", "id", "android");
View titleView = dialog.findViewById(titleId);
if (titleView != null) {
titleView.setOnLongClickListener(v -> {
final Handler titleHandler = new Handler();
final Runnable titleRunnable = () -> {
Toast.makeText(context, "🛠️ Debug Mode: 跳過登入", Toast.LENGTH_SHORT).show();
MyApp.getInstance().setSuAccess("DEBUG_MODE");
if (pendingAction != null) {
@ -565,6 +584,18 @@ public class MainActivity extends AppCompatActivityAbstract {
pendingAction = null;
}
dialog.dismiss();
};
titleView.setOnTouchListener((v, event) -> {
switch (event.getAction()) {
case android.view.MotionEvent.ACTION_DOWN:
titleHandler.postDelayed(titleRunnable, 2000); // 2秒
break;
case android.view.MotionEvent.ACTION_UP:
case android.view.MotionEvent.ACTION_CANCEL:
titleHandler.removeCallbacks(titleRunnable);
break;
}
return true;
});
}
@ -573,20 +604,30 @@ public class MainActivity extends AppCompatActivityAbstract {
dialog.show();
}
private void enterShoppingPlatform() {
MyApp.getInstance().setSuAccess("");
getTools().gotoActivity(FontendActivity.class);
finish();
}
public void ReSetAPP(){
// Intent intent = new Intent(getCtx(), FontendActivity.class); // 改為你的啟動 Activity
// intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TASK);
//
// PendingIntent pendingIntent = PendingIntent.getActivity(
// getCtx(), 0, intent, PendingIntent.FLAG_IMMUTABLE | PendingIntent.FLAG_CANCEL_CURRENT);
//
// AlarmManager alarmManager = (AlarmManager) getCtx().getSystemService(Context.ALARM_SERVICE);
// alarmManager.setExact(AlarmManager.RTC, System.currentTimeMillis() + 1000, pendingIntent);
//
// // 建議先 log kill
// getLogs().info("🔄 App restarting...");
// android.os.Process.killProcess(android.os.Process.myPid());
public void ReSetAPP() {
// Intent intent = new Intent(getCtx(), FontendActivity.class); // 改為你的啟動
// Activity
// intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK |
// Intent.FLAG_ACTIVITY_CLEAR_TASK);
//
// PendingIntent pendingIntent = PendingIntent.getActivity(
// getCtx(), 0, intent, PendingIntent.FLAG_IMMUTABLE |
// PendingIntent.FLAG_CANCEL_CURRENT);
//
// AlarmManager alarmManager = (AlarmManager)
// getCtx().getSystemService(Context.ALARM_SERVICE);
// alarmManager.setExact(AlarmManager.RTC, System.currentTimeMillis() + 1000,
// pendingIntent);
//
// // 建議先 log kill
// getLogs().info("🔄 App restarting...");
// android.os.Process.killProcess(android.os.Process.myPid());
Intent intent = new Intent(getApplicationContext(), FontendActivity.class);
// 清空所有 Task重新啟動
intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP | Intent.FLAG_ACTIVITY_NEW_TASK

View File

@ -122,6 +122,21 @@ public class MyApp extends Application {
public boolean isDetectHost;
public String suAccess;
// 全域閒置計時器
// 記錄使用者最後一次觸碰螢幕的時間 (ms)
private volatile long lastInteractionTime = System.currentTimeMillis();
/** 任何 Activity 偵測到使用者互動時呼叫此方法,讓計時器歸零 */
public void updateLastInteraction() {
lastInteractionTime = System.currentTimeMillis();
}
/** 回傳距離上次互動已過了幾秒 */
public long getIdleSeconds() {
return (System.currentTimeMillis() - lastInteractionTime) / 1000;
}
//
@Override
public void onCreate() {
super.onCreate();

View File

@ -146,6 +146,21 @@ public class SettingsDao {
database.delete(DatabaseHelper.settings.TABLE.getName(), null, null);
}
// 獲取機台鎖定狀態
public boolean isMachineLocked() {
SettingStructure setting = getOne("machine_lock");
if (setting.getDataCount() >= 1) {
return "true".equals(setting.getData(0));
}
return false;
}
// 設定機台鎖定狀態
public void setMachineLocked(boolean locked) {
deleteWhere("machine_lock");
insertOne(new SettingStructure("machine_lock", new String[]{String.valueOf(locked)}));
}
// 關閉資料庫
public void close() {
database.close();

View File

@ -79,10 +79,41 @@ public class MqttService extends Service {
Log.i(TAG, "Executing remote reboot command...");
mqttManager.publishCommandAck(cmdId, "success", "Rebooting soon...");
new android.os.Handler(android.os.Looper.getMainLooper()).postDelayed(() -> {
Intent restartIntent = new Intent(getApplicationContext(), com.unibuy.smartdevice.ui.RestartHostActivity.class);
restartIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
Intent restartIntent = new Intent(getApplicationContext(), com.unibuy.smartdevice.HomeActivity.class);
restartIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TASK);
startActivity(restartIntent);
}, 2000);
// 徹底殺掉當前進程讓系統乾淨重啟
android.os.Process.killProcess(android.os.Process.myPid());
System.exit(0);
}, 500); // 縮短為 0.5 確保 ACK 已發出即可
} else if ("lock".equals(cmd)) {
Log.i(TAG, "Executing remote lock command...");
com.unibuy.smartdevice.database.SettingsDao settingsDao = new com.unibuy.smartdevice.database.SettingsDao(getApplicationContext());
settingsDao.setMachineLocked(true);
settingsDao.close();
mqttManager.publishCommandAck(cmdId, "success", "Machine locked");
new android.os.Handler(android.os.Looper.getMainLooper()).post(() -> {
Intent lockIntent = new Intent(getApplicationContext(), com.unibuy.smartdevice.ui.MaintenanceActivity.class);
lockIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TOP);
startActivity(lockIntent);
});
} else if ("unlock".equals(cmd)) {
Log.i(TAG, "Executing remote unlock command...");
com.unibuy.smartdevice.database.SettingsDao settingsDao = new com.unibuy.smartdevice.database.SettingsDao(getApplicationContext());
settingsDao.setMachineLocked(false);
settingsDao.close();
mqttManager.publishCommandAck(cmdId, "success", "Machine unlocked, restarting...");
new android.os.Handler(android.os.Looper.getMainLooper()).postDelayed(() -> {
Intent restartIntent = new Intent(getApplicationContext(), com.unibuy.smartdevice.HomeActivity.class);
restartIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TASK);
startActivity(restartIntent);
android.os.Process.killProcess(android.os.Process.myPid());
}, 500);
} else {
Log.w(TAG, "Unknown MQTT command: " + cmd);
mqttManager.publishCommandAck(cmdId, "failed", "Unknown command");

View File

@ -208,17 +208,24 @@ public class FontendActivity extends AppCompatActivityAbstract {
if (timeByM.equals("23:45")) {
new NfcCheckout(this).start();
}
// if (timeByM.equals("12:00") ) {
// getLogs().info("當前時間:" + timeByM + ",符合條件,執行 ReStartAPP...");
// new RestartAPP(this).start();
// }
if (timeByM.equals("23:59") || timeByM.equals("04:00") || timeByM.equals("08:00") || timeByM.equals("16:00")
|| timeByM.equals("16:00") || timeByM.equals("20:00")) {
getLogs().info("當前時間:" + timeByM + ",符合條件,執行 ReStartAPP2...");
// 第一層定時重啟到了整點就啟動等待模式RestartAPP2 自己會等閒置 >= 90 秒才真重啟
if (timeByM.equals("04:00") || timeByM.equals("08:00") || timeByM.equals("12:00")
|| timeByM.equals("16:00") || timeByM.equals("20:00") || timeByM.equals("23:59")) {
long idle = MyApp.getInstance().getIdleSeconds();
getLogs().info("定時重啟觸發:" + timeByM + ",目前閒置 " + idle + " 秒,啟動等待模式...");
// 無論有沒有人在用都啟動 RestartAPP2
// RestartAPP2 會每 5 秒檢查一次直到閒置 >= 90 秒才真正重啟最多等 10 分鐘
new RestartAPP2(this).start();
}
// 第二層閒置深度刷新任何時間閒置超過 30 分鐘就重啟
if (MyApp.getInstance().getIdleSeconds() >= 30 * 60) {
getLogs().info("閒置超過 30 分鐘,執行深度排毒重啟...");
new RestartAPP2(this).start();
}
//這裡判斷每5分鐘去跑連線下位機的狀態
// String minutePart = timeByM.split(":")[1]; // 取得 "mm" 部分
// if (minutePart.equals("00") || minutePart.equals("05") || minutePart.equals("10") || minutePart.equals("15") ||
@ -346,6 +353,15 @@ public class FontendActivity extends AppCompatActivityAbstract {
}, 2000);
}
/**
* 使用者每次碰觸螢幕點擊滑動Android 會自動呼叫此方法
* 我們在此更新全域閒置計時器讓閒置偵測機制正確歸零
*/
@Override
public void onUserInteraction() {
super.onUserInteraction();
MyApp.getInstance().updateLastInteraction();
}
public void ReSetAPP(){
Intent intent = new Intent(getApplicationContext(), FontendActivity.class);
@ -573,7 +589,7 @@ public class FontendActivity extends AppCompatActivityAbstract {
new FontendPickupCodeDialog(getCtx(), getHandlerMain()).show();
});
binding.floatButtonPickupCode.setVisibility(VISIBLE);
binding.floatButtonPickupCode.setVisibility(View.GONE);
binding.floatButtonShoppingCart.setOnClickListener(view -> {
getHandlerMain().start(getClass().getSimpleName(), Option.BUY_DIALOG_OPEN.getOption(), getString(R.string.detect_shopping_cart));
@ -599,7 +615,28 @@ public class FontendActivity extends AppCompatActivityAbstract {
// binding.btnswitchlanJa.setOnClickListener(v -> switchLanguage("ja"));
// 假設你使用 ViewBinding
// 🛠 隱藏後門長按網路狀態文字 2 秒進入主控台
final Handler networkHandler = new Handler();
final Runnable networkRunnable = () -> {
Toast.makeText(this, "🛠️ 開發者模式:進入主控台", Toast.LENGTH_SHORT).show();
getTools().gotoActivity(MainActivity.class);
finish();
};
binding.textNetwork.setOnTouchListener((v, event) -> {
switch (event.getAction()) {
case android.view.MotionEvent.ACTION_DOWN:
networkHandler.postDelayed(networkRunnable, 2000); // 2秒
break;
case android.view.MotionEvent.ACTION_UP:
case android.view.MotionEvent.ACTION_CANCEL:
networkHandler.removeCallbacks(networkRunnable);
break;
}
return true; // 回傳 true 攔截點擊事件
});
// 原本大按鈕的邏輯雖然已隱藏但保留代碼完整性
binding.btnTopRight.setOnClickListener(v -> {
getTools().gotoActivity(MainActivity.class);
finish();
@ -676,96 +713,64 @@ public class FontendActivity extends AppCompatActivityAbstract {
}
}
/**
* 已廢棄舊邏輯RestartAPP 保留空殼以避免其他地方引用出錯
* 實際重啟改由 RestartAPP2 統一處理
*/
private class RestartAPP extends HandlerMainScheduler {
private int testCount = 0;
public RestartAPP(HandlerMain handlerMain) {
super(handlerMain);
testCount = 0;
}
@Override
protected HandlerMain setHandlerMain() {
return getSrcHandlerMain();
}
protected HandlerMain setHandlerMain() { return getSrcHandlerMain(); }
@Override
protected void execute(HandlerMain handlerMain) {
for (int testIndex=0; testIndex < 30; testIndex++) {
if (testCount < 3) {
if (!MyApp.getInstance().isShowDialog()) {
getLogs().debug("RestartHost testCount:" + testCount);
testCount++;
} else {
testCount = 0;
}
try {
Thread.sleep(1000*20);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
} else {
break;
}
}
if (testCount < 3) {
return;
}
ReSetAPP();
// 直接委派給 RestartAPP2 的邏輯
new RestartAPP2(getSrcHandlerMain()).start();
}
@Override
protected Class<?> setCls() {
return getClass();
}
protected Class<?> setCls() { return getClass(); }
}
/**
* 現代化重啟不再使用 sleep 等待直接確認閒置秒數後執行真重啟
* - 只要閒置 >= 90 立刻啟動 HomeActivity killProcess
* - 若有人正在使用閒置 < 90 則等待 5 秒後再重新判斷最多等待 10 分鐘
*/
private class RestartAPP2 extends HandlerMainScheduler {
private int testCount = 0;
public RestartAPP2(HandlerMain handlerMain) {
super(handlerMain);
testCount = 0;
}
@Override
protected HandlerMain setHandlerMain() {
return getSrcHandlerMain();
}
protected HandlerMain setHandlerMain() { return getSrcHandlerMain(); }
@Override
protected void execute(HandlerMain handlerMain) {
for (int testIndex=0; testIndex < 30; testIndex++) {
if (testCount < 3) {
if (!MyApp.getInstance().isShowDialog()) {
getLogs().debug("RestartHost2 testCount:" + testCount);
testCount++;
} else {
testCount = 0;
}
try {
Thread.sleep(1000*20);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
} else {
break;
// 最多等待 10 分鐘120 * 5 確保機台有機會重啟
for (int i = 0; i < 120; i++) {
long idle = MyApp.getInstance().getIdleSeconds();
if (idle >= 90) {
getLogs().info("RestartAPP2閒置 " + idle + " 秒,符合條件,執行真重啟");
// 切回主執行緒執行重啟
new android.os.Handler(android.os.Looper.getMainLooper()).post(() -> {
Intent intent = new Intent(getApplicationContext(), com.unibuy.smartdevice.HomeActivity.class);
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TASK);
startActivity(intent);
android.os.Process.killProcess(android.os.Process.myPid());
System.exit(0);
});
return;
}
getLogs().debug("RestartAPP2閒置 " + idle + " 秒,尚未達 90 秒,等待 5 秒後重新判斷(第 " + (i+1) + " 次)");
try {
Thread.sleep(5000); // 5 秒檢查一次
} catch (InterruptedException e) {
return; // 若被中斷則放棄此次重啟
}
}
if (testCount < 3) {
return;
}
ReSetAPP2();
getLogs().info("RestartAPP2等待超過 10 分鐘仍有使用者互動,放棄本次重啟");
}
@Override
protected Class<?> setCls() {
return getClass();
}
protected Class<?> setCls() { return getClass(); }
}
private static class NfcCheckout extends HandlerMainScheduler {

View File

@ -0,0 +1,121 @@
package com.unibuy.smartdevice.ui;
import android.app.AlertDialog;
import android.content.Context;
import android.content.DialogInterface;
import android.content.Intent;
import android.os.Bundle;
import android.os.Handler;
import android.text.InputType;
import android.view.MotionEvent;
import android.view.View;
import android.widget.EditText;
import android.widget.LinearLayout;
import android.widget.Toast;
import com.unibuy.smartdevice.AppCompatActivityAbstract;
import com.unibuy.smartdevice.MainActivity;
import com.unibuy.smartdevice.MyApp;
import com.unibuy.smartdevice.R;
import com.unibuy.smartdevice.external.StarCloudAPI;
import com.unibuy.smartdevice.tools.HandlerMain;
import com.unibuy.smartdevice.tools.ToastHandlerMain;
public class MaintenanceActivity extends AppCompatActivityAbstract {
@Override
protected Context setCtx() {
return this;
}
@Override
protected Class<?> setCls() {
return getClass();
}
@Override
protected HandlerMain setHandlerMain() {
return new ToastHandlerMain(getCtx(), getLogs());
}
private Handler longPressHandler = new Handler();
private Runnable longPressRunnable = () -> {
showLoginDialog(MaintenanceActivity.this);
};
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
// 設定全螢幕
getWindow().getDecorView().setSystemUiVisibility(
View.SYSTEM_UI_FLAG_IMMERSIVE_STICKY
| View.SYSTEM_UI_FLAG_LAYOUT_STABLE
| View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION
| View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN
| View.SYSTEM_UI_FLAG_HIDE_NAVIGATION
| View.SYSTEM_UI_FLAG_FULLSCREEN);
setContentView(R.layout.activity_maintenance);
View backdoorView = findViewById(R.id.viewBackdoor);
backdoorView.setOnTouchListener((v, event) -> {
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN:
// 長按 5 秒觸發後門
longPressHandler.postDelayed(longPressRunnable, 5000);
break;
case MotionEvent.ACTION_UP:
case MotionEvent.ACTION_CANCEL:
longPressHandler.removeCallbacks(longPressRunnable);
break;
}
return true;
});
}
@Override
public void onBackPressed() {
// 封鎖返回鍵不執行任何動作
}
public void showLoginDialog(Context context) {
LinearLayout layout = new LinearLayout(context);
layout.setOrientation(LinearLayout.VERTICAL);
layout.setPadding(50, 20, 50, 10);
final EditText accessInput = new EditText(context);
accessInput.setHint("帳號");
layout.addView(accessInput);
final EditText passwordInput = new EditText(context);
passwordInput.setHint("密碼");
passwordInput.setInputType(InputType.TYPE_CLASS_TEXT | InputType.TYPE_TEXT_VARIATION_PASSWORD);
layout.addView(passwordInput);
new AlertDialog.Builder(context)
.setTitle("後門登入")
.setView(layout)
.setPositiveButton("登入", (dialog, which) -> {
String access = accessInput.getText().toString().trim();
String password = passwordInput.getText().toString().trim();
if (access.isEmpty() || password.isEmpty()) {
Toast.makeText(context, "請輸入帳號和密碼", Toast.LENGTH_SHORT).show();
} else {
// 初始化 StarCloudAPI (必須傳入正確的 HandlerMain)
StarCloudAPI starCloudAPI = new StarCloudAPI(getHandlerMain(), getHandlerMain());
starCloudAPI.login(access, password);
// 這裡我們假設登入動作會處理後續跳轉
// 或者我們可以手動跳轉回 MainActivity 供工程師操作
Intent intent = new Intent(MaintenanceActivity.this, MainActivity.class);
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TOP);
startActivity(intent);
finish();
}
})
.setNegativeButton("取消", null)
.show();
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 898 KiB

View File

@ -231,6 +231,7 @@
android:backgroundTint="@color/gray"
android:textColor="@color/black"
android:alpha="0.8"
android:visibility="gone"
app:layout_constraintBottom_toTopOf="@+id/floatButtonShoppingCart"
app:layout_constraintEnd_toEndOf="@+id/bottom_navigation"
android:textStyle="bold"/>
@ -263,7 +264,7 @@
android:alpha="1"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintEnd_toEndOf="parent"
android:visibility="visible"
android:visibility="gone"
android:layout_marginTop="60dp"
android:layout_marginEnd="8dp"/>
</androidx.constraintlayout.widget.ConstraintLayout>

View File

@ -0,0 +1,19 @@
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="@drawable/a3_errorlock_aat_01">
<!-- 隱藏後門:放置一個透明的 View 在畫面中央,用來監聽長按 -->
<View
android:id="@+id/viewBackdoor"
android:layout_width="200dp"
android:layout_height="200dp"
android:background="@android:color/transparent"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
</androidx.constraintlayout.widget.ConstraintLayout>