diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml
index 34074c9..ffbae8a 100644
--- a/app/src/main/AndroidManifest.xml
+++ b/app/src/main/AndroidManifest.xml
@@ -80,6 +80,11 @@
android:name=".ui.RestartActivity"
android:theme="@style/Theme.ImageBackground"
android:exported="false" />
+
diff --git a/app/src/main/java/com/unibuy/smartdevice/HomeActivity.java b/app/src/main/java/com/unibuy/smartdevice/HomeActivity.java
index bb3d174..be7a986 100644
--- a/app/src/main/java/com/unibuy/smartdevice/HomeActivity.java
+++ b/app/src/main/java/com/unibuy/smartdevice/HomeActivity.java
@@ -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());
diff --git a/app/src/main/java/com/unibuy/smartdevice/MainActivity.java b/app/src/main/java/com/unibuy/smartdevice/MainActivity.java
index 92da9c5..512456e 100644
--- a/app/src/main/java/com/unibuy/smartdevice/MainActivity.java
+++ b/app/src/main/java/com/unibuy/smartdevice/MainActivity.java
@@ -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 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 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
diff --git a/app/src/main/java/com/unibuy/smartdevice/MyApp.java b/app/src/main/java/com/unibuy/smartdevice/MyApp.java
index 210306c..87933ae 100644
--- a/app/src/main/java/com/unibuy/smartdevice/MyApp.java
+++ b/app/src/main/java/com/unibuy/smartdevice/MyApp.java
@@ -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();
diff --git a/app/src/main/java/com/unibuy/smartdevice/database/SettingsDao.java b/app/src/main/java/com/unibuy/smartdevice/database/SettingsDao.java
index 336dd36..923b4e5 100644
--- a/app/src/main/java/com/unibuy/smartdevice/database/SettingsDao.java
+++ b/app/src/main/java/com/unibuy/smartdevice/database/SettingsDao.java
@@ -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();
diff --git a/app/src/main/java/com/unibuy/smartdevice/service/MqttService.java b/app/src/main/java/com/unibuy/smartdevice/service/MqttService.java
index 53442cc..70fa1cf 100644
--- a/app/src/main/java/com/unibuy/smartdevice/service/MqttService.java
+++ b/app/src/main/java/com/unibuy/smartdevice/service/MqttService.java
@@ -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");
diff --git a/app/src/main/java/com/unibuy/smartdevice/ui/FontendActivity.java b/app/src/main/java/com/unibuy/smartdevice/ui/FontendActivity.java
index 14feea9..883f9e5 100644
--- a/app/src/main/java/com/unibuy/smartdevice/ui/FontendActivity.java
+++ b/app/src/main/java/com/unibuy/smartdevice/ui/FontendActivity.java
@@ -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 {
diff --git a/app/src/main/java/com/unibuy/smartdevice/ui/MaintenanceActivity.java b/app/src/main/java/com/unibuy/smartdevice/ui/MaintenanceActivity.java
new file mode 100644
index 0000000..d4d9244
--- /dev/null
+++ b/app/src/main/java/com/unibuy/smartdevice/ui/MaintenanceActivity.java
@@ -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();
+ }
+}
diff --git a/app/src/main/res/drawable/a3_errorlock_aat_01.png b/app/src/main/res/drawable/a3_errorlock_aat_01.png
new file mode 100644
index 0000000..04e48bf
Binary files /dev/null and b/app/src/main/res/drawable/a3_errorlock_aat_01.png differ
diff --git a/app/src/main/res/layout/activity_fontend.xml b/app/src/main/res/layout/activity_fontend.xml
index 90b2374..3fa14b3 100644
--- a/app/src/main/res/layout/activity_fontend.xml
+++ b/app/src/main/res/layout/activity_fontend.xml
@@ -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"/>
\ No newline at end of file
diff --git a/app/src/main/res/layout/activity_maintenance.xml b/app/src/main/res/layout/activity_maintenance.xml
new file mode 100644
index 0000000..78842e7
--- /dev/null
+++ b/app/src/main/res/layout/activity_maintenance.xml
@@ -0,0 +1,19 @@
+
+
+
+
+
+
+