[FEAT]:新增遠端維護鎖定模式與隱藏後門機制
1. 實作遠端維護鎖定功能,支援 MQTT lock 與 unlock 指令。 2. 新增 MaintenanceActivity 頁面,顯示「機器維護中」背景圖並封鎖所有操作。 3. 在維護頁面實作隱藏後門機制(長按畫面中央 5 秒觸發管理員登入視窗)。 4. 更新 SettingsDao 使用 SQLite 持久化機台鎖定狀態,確保重啟後依然生效。 5. 更新 HomeActivity 入口邏輯,在開機啟動時優先檢查鎖定狀態。 6. 修正維護頁面資源路徑,並清理干擾編譯的 NTFS Zone Identifier 檔案。
This commit is contained in:
parent
d722b3273b
commit
18943e8a0f
@ -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">
|
||||
|
||||
@ -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());
|
||||
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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();
|
||||
|
||||
@ -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();
|
||||
|
||||
@ -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");
|
||||
|
||||
@ -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 {
|
||||
|
||||
@ -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();
|
||||
}
|
||||
}
|
||||
BIN
app/src/main/res/drawable/a3_errorlock_aat_01.png
Normal file
BIN
app/src/main/res/drawable/a3_errorlock_aat_01.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 898 KiB |
@ -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>
|
||||
19
app/src/main/res/layout/activity_maintenance.xml
Normal file
19
app/src/main/res/layout/activity_maintenance.xml
Normal 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>
|
||||
Loading…
Reference in New Issue
Block a user