package com.xypower.mpapp; import android.Manifest; import android.content.Context; import android.content.DialogInterface; import android.content.Intent; import android.content.pm.PackageManager; import android.location.Location; import android.location.LocationListener; import android.location.LocationManager; import android.os.Build; import android.os.FileObserver; import android.os.Handler; import android.os.Looper; import android.os.Message; import android.os.Messenger; import android.os.StrictMode; import androidx.annotation.NonNull; import androidx.appcompat.app.ActionBar; import androidx.appcompat.app.AlertDialog; import androidx.core.app.ActivityCompat; import androidx.appcompat.app.AppCompatActivity; import android.os.Bundle; import android.telephony.SubscriptionManager; import android.telephony.TelephonyManager; import android.text.TextUtils; import android.text.method.ScrollingMovementMethod; import android.util.Log; import android.view.KeyEvent; import android.view.View; import android.view.WindowManager; import android.widget.Toast; import com.dev.devapi.api.SysApi; import com.xypower.common.CameraUtils; import com.xypower.common.MicroPhotoContext; import com.xypower.mpapp.databinding.ActivityMainBinding; import com.xypower.mpapp.utils.LocationUtil; import com.xypower.mpapp.utils.RandomReader; import java.io.File; import java.lang.reflect.Method; import java.text.SimpleDateFormat; import java.util.Date; public class MainActivity extends AppCompatActivity { public static final String TAG = "MPLOG"; private static int MY_PERMISSIONS_REQUEST_FOREGROUND_SERVICE = 100; public static final int MSG_WHAT_LOG_OBSERVER = MicroPhotoService.MSG_WHAT_MAX + 10; // Used to load the 'microphoto' library on application startup. public static final int MAX_LOG_LINES = 480; public static final int MIN_LOG_LINES = 120; private ActivityMainBinding binding; private Handler mHandler = null; private Messenger mMessenger = null; private LogFileObserver mLogFileObserver = null; private class LogFileObserver extends FileObserver { private long mOffset = 0; private String mPath = null; public LogFileObserver(String path) { super(path, FileObserver.MODIFY | FileObserver.CREATE); mPath = path; File file = new File(path); if (file.exists()) { mOffset = file.length(); } } @Override public void onEvent(int event, String s) { int e = event & FileObserver.ALL_EVENTS; if (e == FileObserver.MODIFY) { File file = new File(mPath); long newOffset = file.length(); if (newOffset > mOffset) { RandomReader reader = new RandomReader(mPath, mOffset); byte[] bytes = new byte[(int)(newOffset - mOffset)]; int bytesRead = reader.read(bytes); mOffset += bytesRead; Message msg = Message.obtain(); msg.what = MSG_WHAT_LOG_OBSERVER; msg.obj = bytes; msg.arg1 = bytesRead; mHandler.sendMessage(msg); } } else if (e == FileObserver.CREATE) { mOffset = 0; } } } @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.R) { int activeSubId = SubscriptionManager.getActiveDataSubscriptionId(); if (activeSubId == -1) { MicroPhotoContext.selectSimCard(getApplicationContext(), 1); } } binding = ActivityMainBinding.inflate(getLayoutInflater()); setContentView(binding.getRoot()); // getWindow().setSoftInputMode(WindowManager.LayoutParams.SOFT_INPUT_STATE_ALWAYS_HIDDEN); getWindow().addFlags(WindowManager.LayoutParams.FLAG_ALT_FOCUSABLE_IM); ActionBar actionBar = getSupportActionBar(); Date date = new Date(BuildConfig.BUILD_TIMESTAMP); SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm"); actionBar.setTitle(actionBar.getTitle().toString() + " v" + MicroPhotoContext.getVersionName(getApplicationContext()) + " " + sdf.format(date)); binding.logs.setText(""); binding.logs.setMovementMethod(ScrollingMovementMethod.getInstance()); binding.logs.setScrollbarFadingEnabled(false); mHandler = new Handler(Looper.myLooper()) { @Override public void handleMessage(@NonNull Message msg) { switch (msg.what) { case MSG_WHAT_LOG_OBSERVER: { byte[] bytes = (byte[])msg.obj; int bytesRead = msg.arg1; String log = null; try { log = new String(bytes, 0, bytesRead, "UTF-8"); } catch (Exception e) { e.printStackTrace(); } if (log != null) { binding.logs.append(log); int offset = binding.logs.getLineCount() * binding.logs.getLineHeight(); if (offset > binding.logs.getHeight()) { binding.logs.scrollTo(0, offset - binding.logs.getHeight() + binding.logs.getLineHeight()); } } } break; } } }; StrictMode.ThreadPolicy policy = new StrictMode.ThreadPolicy.Builder().permitAll().build(); StrictMode.setThreadPolicy(policy); Intent intent = getIntent(); final int noDelay = intent.getIntExtra("noDelay", 0); int rebootFlag = intent.getIntExtra("reboot", 0); String reason = intent.getStringExtra("reason"); if (!TextUtils.isEmpty(reason)) { Log.w(TAG, "App Started with reason: " + reason); } if (rebootFlag == 1) { Log.i(TAG, "After Reboot"); } Log.d(TAG, "MainActivity: reboot=" + rebootFlag + " noDelay=" + noDelay); final MicroPhotoContext.AppConfig appConfig = getAppConfig(); if (TextUtils.isEmpty(appConfig.cmdid)) { appConfig.cmdid = MicroPhotoService.getSerialNumber(); binding.cmdid.setText(appConfig.cmdid); } else { binding.cmdid.setText(appConfig.cmdid); } binding.server.setText(appConfig.server); binding.port.setText(appConfig.port != 0 ? Integer.toString(appConfig.port) : ""); String protocolStr = appConfig.protocol + "-"; for (int idx = 0; idx < binding.protocol.getCount(); idx++) { String item = binding.protocol.getItemAtPosition(idx).toString(); if (item.startsWith(protocolStr)) { binding.protocol.setSelection(idx); break; } } if (appConfig.networkProtocol < binding.networkProtocol.getCount()) { binding.networkProtocol.setSelection(appConfig.networkProtocol); } if (appConfig.encryption < binding.encryptions.getCount()) { binding.encryptions.setSelection(appConfig.encryption); } binding.heartbeat.setText((appConfig.heartbeat > 0) ? Integer.toString(appConfig.heartbeat) : ""); binding.packetSize.setText((appConfig.packetSize > 0) ? Integer.toString(appConfig.packetSize) : ""); if (appConfig.network < binding.network.getCount()) { binding.network.setSelection(appConfig.network); } this.binding.btnStartServ.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View view) { String[] accessPermissions = new String[]{Manifest.permission.CAMERA, Manifest.permission.WRITE_EXTERNAL_STORAGE, Manifest.permission.FOREGROUND_SERVICE, Manifest.permission.READ_PHONE_STATE, /*Manifest.permission.PACKAGE_USAGE_STATS,*/ /*Manifest.permission.SET_TIME,*/}; boolean needRequire = false; for (String access : accessPermissions) { int curPermission = ActivityCompat.checkSelfPermission(MainActivity.this, access); if (curPermission != PackageManager.PERMISSION_GRANTED) { needRequire = true; break; } } if (needRequire) { ActivityCompat.requestPermissions(MainActivity.this, accessPermissions, MY_PERMISSIONS_REQUEST_FOREGROUND_SERVICE); // return; } binding.logs.setText(""); MicroPhotoContext.AppConfig curAppConfig = retrieveAndSaveAppConfig(); // TakeAndThrowPhoto(2, 0xFF); try { // Thread.sleep(20); } catch (Exception ex) { ex.printStackTrace(); } startMicroPhotoService(MainActivity.this.getApplicationContext(), appConfig, mMessenger); binding.btnStartServ.setEnabled(false); binding.btnStopServ.setEnabled(true); } }); this.binding.btnSaveCfg.setOnClickListener(new View.OnClickListener() { @Override public void onClick(final View view) { MicroPhotoContext.AppConfig appCfg = MicroPhotoContext.getMpAppConfig(getApplicationContext()); String protocolStr = MainActivity.this.binding.protocol.getSelectedItem().toString(); int protocol = MicroPhotoContext.DEFAULT_PROTOCOL; String[] parts = protocolStr.split("-"); if (parts != null) { protocol = Integer.parseInt(parts[0]); } if (appCfg.protocol != protocol) { AlertDialog.Builder builder = new AlertDialog.Builder(view.getContext()); builder.setTitle(R.string.confirm_change_protocol); builder.setMessage(R.string.confirm_change_protocol_text); builder.setCancelable(true); builder.setPositiveButton(R.string.btn_ok, new DialogInterface.OnClickListener() { @Override public void onClick(DialogInterface dialogInterface, int i) { dialogInterface.dismiss(); retrieveAndSaveAppConfig(); MicroPhotoContext.removeMpConfigFiles(getApplicationContext()); MicroPhotoService.updateConfigs(MainActivity.this.getApplicationContext()); } }); builder.setNegativeButton(R.string.btn_cancel, new DialogInterface.OnClickListener() { @Override public void onClick(DialogInterface dialogInterface, int i) { dialogInterface.dismiss(); } }); builder.show(); } else { retrieveAndSaveAppConfig(); MicroPhotoService.updateConfigs(MainActivity.this.getApplicationContext()); } } }); this.binding.btnTakePhoto.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View view) { takePhoto(1, 255, true); } }); this.binding.btnTakePhoto2.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View view) { takePhoto(2, 255, true); } }); this.binding.btnTakePhoto3.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View view) { takePhoto(3, 255, true); } }); this.binding.btnTakePhoto4.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View view) { takePhoto(4, 255, true); } }); this.binding.takeVideoBtn.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View view) { takePhoto(1, 255, false); } }); this.binding.takeVideoBtn2.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View view) { takePhoto(2, 255, false); } }); this.binding.takeVideoBtn3.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View view) { takePhoto(3, 255, false); } }); this.binding.takeVideoBtn4.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View view) { takePhoto(4, 255, false); } }); this.binding.btnStopServ.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View view) { MicroPhotoService.stopTerminalService(getApplicationContext()); binding.btnStartServ.setEnabled(true); binding.btnStopServ.setEnabled(false); } }); binding.btnChannels.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { Intent intent = new Intent(MainActivity.this, ChannelActivity.class); startActivity(intent); } }); if (MicroPhotoService.isRunning) { Intent intent2 = new Intent(MainActivity.this, MicroPhotoService.class); try { stopService(intent2); } catch (Exception ex) { ex.printStackTrace(); } } Runnable runnable = new Runnable() { @Override public void run() { if (!MicroPhotoService.isRunning && !TextUtils.isEmpty(appConfig.cmdid) && !TextUtils.isEmpty(appConfig.server) && appConfig.port != 0) { if (binding.btnStartServ.isEnabled()) { binding.btnStartServ.performClick(); } } } }; mHandler.postDelayed(runnable, noDelay != 0 ? 1000 : 5000); binding.btnStartServ.setEnabled(!MicroPhotoService.isRunning); binding.btnStopServ.setEnabled(MicroPhotoService.isRunning); binding.btnSendHb.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { MicroPhotoService.sendHeartbeat(getApplicationContext()); } }); binding.btnRestartApp.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { Context context = v.getContext().getApplicationContext(); MicroPhotoService.restartApp(context, context.getPackageName(), "Manual Restart From MainActivity"); } }); binding.btnReboot.setOnClickListener(new View.OnClickListener() { @Override public void onClick(final View v) { AlertDialog.Builder builder = new AlertDialog.Builder(v.getContext()); builder.setTitle(R.string.confirm_reboot); builder.setMessage(R.string.text_confirm_reboot); builder.setCancelable(true); builder.setPositiveButton(R.string.btn_ok, new DialogInterface.OnClickListener() { @Override public void onClick(DialogInterface dialogInterface, int i) { dialogInterface.dismiss(); SysApi.reboot(v.getContext().getApplicationContext()); } }); builder.setNegativeButton(R.string.btn_cancel, new DialogInterface.OnClickListener() { @Override public void onClick(DialogInterface dialogInterface, int i) { dialogInterface.dismiss(); } }); builder.show(); } }); binding.btnCameraInfo.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View view) { MicroPhotoService.setOtgState(true); // SysApi.setOtgState(true); MicroPhotoService.setCam3V3Enable(true); Runnable runnable = new Runnable() { @Override public void run() { String cameraInfo = CameraUtils.getAllCameraInfo(view.getContext()); Log.d(TAG, cameraInfo); MicroPhotoService.setCam3V3Enable(false); MicroPhotoService.setOtgState(false); binding.logs.append(cameraInfo + "\r\n"); Toast.makeText(view.getContext(), cameraInfo, Toast.LENGTH_LONG).show(); } }; mHandler.postDelayed(runnable, 1500); } }); } @Override protected void onDestroy() { super.onDestroy(); } public static void startMicroPhotoService(Context context, MicroPhotoContext.AppConfig curAppConfig, Messenger messenger) { if (TextUtils.isEmpty(curAppConfig.cmdid) || TextUtils.isEmpty(curAppConfig.server) || curAppConfig.port == 0) { return; } Intent intent = new Intent(context, MicroPhotoService.class); intent.setAction(MicroPhotoService.ACTION_START); intent.putExtra("cmdid", curAppConfig.cmdid); intent.putExtra("server", curAppConfig.server); intent.putExtra("port", curAppConfig.port); intent.putExtra("protocol", curAppConfig.protocol); intent.putExtra("networkProtocol", curAppConfig.networkProtocol); intent.putExtra("encryption", curAppConfig.encryption); intent.putExtra("network", curAppConfig.network); if (messenger != null) { intent.putExtra("messenger", messenger); } if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { context.startForegroundService(intent); } else { context.startService(intent); } } private void takePhoto(int channel, int preset, boolean photoOrVideo) { if (binding.btnStartServ.isEnabled()) { String appPath = MicroPhotoContext.buildMpAppDir(getApplicationContext()); File file = new File(appPath); File tempFile = new File(file, "tmp"); if (!tempFile.exists()) { tempFile.mkdirs(); } SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyyMMddhhmmss"); Date dt = new Date(); File photoFile = new File(tempFile, "IMG-" + Integer.toString(channel) + "-" + simpleDateFormat.format(dt) + (photoOrVideo ? ".jpg" : ".mp4")); if (photoFile.exists()) { photoFile.delete(); } File configFile = new File(appPath); configFile = new File(configFile, "data/channels/" + Integer.toString(channel) + ".json"); MicroPhotoService.takePhoto(channel, preset, photoOrVideo, configFile.getAbsolutePath(), photoFile.getAbsolutePath()); } else { MicroPhotoService.takePhoto(this.getApplicationContext(), channel, preset, photoOrVideo); } } protected void TakeAndThrowPhoto(int channel, int preset) { String appPath = MicroPhotoContext.buildMpAppDir(getApplicationContext()); File configFile = new File(appPath); configFile = new File(configFile, "data/channels/" + Integer.toString(channel) + ".json"); SimpleDateFormat sdf = new SimpleDateFormat("yyyyMMddHHmmss"); Date dt = new Date(); File tmpPath = new File(appPath, "tmp"); if (!tmpPath.exists()) { tmpPath.mkdirs(); } File photoFile = new File(tmpPath, "IMG-" + Integer.toString(channel) + "-" + sdf.format(dt) + ".jpg"); if (photoFile.exists()) { photoFile.delete(); } MicroPhotoService.takePhoto(channel, preset, true, configFile.getAbsolutePath(), photoFile.getAbsolutePath()); } @Override protected void onResume() { // call the superclass method first super.onResume(); try { String logFilePath = MicroPhotoContext.buildAppDir(this.getApplicationContext()); logFilePath += "logs"; File file = new File(logFilePath); if (!file.exists()) { file.mkdirs(); } logFilePath += "/log.txt"; file = new File(logFilePath); if (!file.exists()) { file.createNewFile(); } mLogFileObserver = new LogFileObserver(logFilePath); mLogFileObserver.startWatching(); Log.i(TAG, "Log Observer Started"); int lines = binding.logs.getLineCount(); if (lines > MAX_LOG_LINES) { int excessLineNumber = lines - MIN_LOG_LINES; int eolIndex = -1; CharSequence charSequence = binding.logs.getText(); for (int i = 0; i < excessLineNumber; i++) { do { eolIndex++; } while (eolIndex < charSequence.length() && charSequence.charAt(eolIndex) != '\n'); } if (eolIndex < charSequence.length()) { binding.logs.getEditableText().delete(0, eolIndex + 1); } } } catch (Exception e) { e.printStackTrace(); } } @Override protected void onPause() { // call the superclass method first super.onPause(); try { if (mLogFileObserver != null) { mLogFileObserver.stopWatching(); mLogFileObserver = null; Log.i(TAG, "Log Observer Stopped"); } } catch (Exception e) { e.printStackTrace(); } } private MicroPhotoContext.AppConfig retrieveAndSaveAppConfig() { MicroPhotoContext.AppConfig appConfig = new MicroPhotoContext.AppConfig(); appConfig.cmdid = MainActivity.this.binding.cmdid.getText().toString(); appConfig.server = MainActivity.this.binding.server.getText().toString(); String portStr = MainActivity.this.binding.port.getText().toString(); appConfig.port = TextUtils.isEmpty(portStr) ? 0 : Integer.parseInt(portStr); String protocolStr = MainActivity.this.binding.protocol.getSelectedItem().toString(); appConfig.protocol = MicroPhotoContext.DEFAULT_PROTOCOL; String[] parts = protocolStr.split("-"); if (parts != null) { appConfig.protocol = Integer.parseInt(parts[0]); } appConfig.networkProtocol = MainActivity.this.binding.networkProtocol.getSelectedItemPosition(); appConfig.encryption = MainActivity.this.binding.encryptions.getSelectedItemPosition(); appConfig.heartbeat = TextUtils.isEmpty(binding.heartbeat.getText().toString()) ? 0 : Integer.parseInt(binding.heartbeat.getText().toString()); appConfig.packetSize = TextUtils.isEmpty(binding.packetSize.getText().toString()) ? 0 : Integer.parseInt(binding.packetSize.getText().toString()); appConfig.network = MainActivity.this.binding.network.getSelectedItemPosition(); saveAppConfig(appConfig); return appConfig; } private MicroPhotoContext.AppConfig getAppConfig() { return MicroPhotoContext.getMpAppConfig(getApplicationContext()); } private void saveAppConfig(MicroPhotoContext.AppConfig appConfig) { MicroPhotoContext.saveMpAppConfig(getApplicationContext(), appConfig); } private int getDefaultDataSubId() { SubscriptionManager subscriptionManager = (SubscriptionManager) getSystemService(Context.TELEPHONY_SUBSCRIPTION_SERVICE); try { Method method = subscriptionManager.getClass().getDeclaredMethod("getDefaultDataSubscriptionId"); return (int) method.invoke(subscriptionManager); } catch (Exception e) { Log.e(TAG, "wjz debug getDefaultDataSubId: error is " + e.getMessage()); } return 0; } LocationListener locationListener = new LocationListener() { @Override public void onLocationChanged(Location location) { // 处理位置更新事件 double latitude = location.getLatitude(); double longitude = location.getLongitude(); // ... Log.e("xyh", "定位方式:" + location.getProvider()); Log.e("xyh", "纬度:" + location.getLatitude()); Log.e("xyh", "经度:" + location.getLongitude()); Log.e("xyh", "海拔:" + location.getAltitude()); Log.e("xyh", "时间:" + location.getTime()); Log.e("xyh", "国家:" + LocationUtil.getCountryName(MainActivity.this, location.getLatitude(), location.getLongitude())); Log.e("xyh", "获取地理位置:" + LocationUtil.getAddress(MainActivity.this, location.getLatitude(), location.getLongitude())); Log.e("xyh", "所在地:" + LocationUtil.getLocality(MainActivity.this, location.getLatitude(), location.getLongitude())); Log.e("xyh", "所在街道:" + LocationUtil.getStreet(MainActivity.this, location.getLatitude(), location.getLongitude())); } @Override public void onStatusChanged(String provider, int status, Bundle extras) { // 处理位置状态变化事件 } @Override public void onProviderEnabled(String provider) { // 处理位置提供者启用事件 } @Override public void onProviderDisabled(String provider) { // 处理位置提供者禁用事件 } }; @Override public boolean onKeyDown(int keyCode, KeyEvent event) { if (keyCode == KeyEvent.KEYCODE_BACK) { // Back pressed return true; // disable it } return super.onKeyDown(keyCode, event); } }