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 @@ + + + + + + +