package org.las2mile.scrcpy; import android.annotation.SuppressLint; import android.app.Activity; import android.content.ComponentName; import android.content.Context; import android.content.Intent; import android.content.ServiceConnection; import android.content.pm.ActivityInfo; import android.content.res.AssetManager; import android.hardware.Sensor; import android.hardware.SensorEvent; import android.hardware.SensorEventListener; import android.hardware.SensorManager; import android.os.Bundle; import android.os.IBinder; import android.os.SystemClock; import android.util.Base64; import android.util.DisplayMetrics; import android.util.Log; import android.view.Display; import android.view.Surface; import android.view.SurfaceView; import android.view.View; import android.view.ViewConfiguration; import android.widget.AdapterView; import android.widget.ArrayAdapter; import android.widget.Button; import android.widget.EditText; import android.widget.LinearLayout; import android.widget.Spinner; import android.widget.Switch; import android.widget.Toast; import java.io.IOException; import java.io.InputStream; import java.net.Inet4Address; import java.net.Inet6Address; import java.net.InetAddress; import java.net.NetworkInterface; import java.net.SocketException; import java.util.Enumeration; import android.app.ProgressDialog; import android.os.Handler; import android.os.Message; import org.las2mile.okio.message.BaseMsg; import org.las2mile.okio.message.resp.HartBeatMsgResp; import org.las2mile.okio.message.resp.LogFileEndMsgResp; import org.las2mile.okio.message.resp.LogFileMsgResp; import android.widget.TextView; import java.util.Set; import com.tbruyelle.rxpermissions3.RxPermissions; import androidx.fragment.app.FragmentActivity; import android.Manifest; import android.app.AlertDialog; import android.content.DialogInterface; import android.net.Uri; import android.provider.Settings; import android.graphics.Color; import org.las2mile.okio.utils.ZipUtils; public class MainActivity extends FragmentActivity implements Scrcpy.ServiceCallbacks, SensorEventListener { private static final String PREFERENCE_KEY = "default"; private static final String PREFERENCE_SPINNER_RESOLUTION = "spinner_resolution"; private static final String PREFERENCE_SPINNER_BITRATE = "spinner_bitrate"; private static int screenWidth; private static int screenHeight; private static boolean landscape = false; private static boolean first_time = true; private static boolean result_of_Rotation = false; private static boolean serviceBound = false; private static boolean nav = false; SensorManager sensorManager; private SendCommands sendCommands; private int videoBitrate; private String local_ip; private Context context; private String serverAdr = null; private String serverPort = null; private SurfaceView surfaceView; private Surface surface; private Scrcpy scrcpy; private long timestamp = 0; private byte[] fileBase64; private static float remote_device_width; private static float remote_device_height; private LinearLayout linearLayout; private static boolean no_control = false; private RxPermissions rxPermissions; ProgressDialog mainProgressDialog = null; static final int ADB_CONNECT_START = 1; static final int ADB_CONNECT_REAL_START = 2; static final int ADB_CONNECT_SUC = 3; static final int ADB_CONNECT_FAIL = 4; static final int ADB_CONNECT_FAIL_HIDE = 5; static final int SERVER_ENV_PREPARED = 6; static final int GOT_REMOTE_VERSION = 7; static final int GOT_REMOTE_LOG_PROCEDURE = 8; static final int GOT_REMOTE_LOG_PROCEDURE_END = 9; static final int GOT_REMOTE_LOG_PROCEDURE_START = 10; Handler mainHandler = new Handler(new Handler.Callback() { @Override public boolean handleMessage(Message message) { int what = message.what; Log.e("scrcpy",mainHandler+" Asset ADB_CONNECT_SUC "+what); if(what == ADB_CONNECT_START){ showProgress(); mainHandler.sendEmptyMessageDelayed(ADB_CONNECT_REAL_START,500); } else if(what == ADB_CONNECT_REAL_START){ sendCommands.SendAdbCommands(context, fileBase64, serverAdr, Integer.parseInt(serverPort), local_ip, videoBitrate, Math.max(screenHeight, screenWidth)); } else if(what == ADB_CONNECT_SUC){ setProgressMsg("与公司设备连接成功,正在初始化公司设备..."); } else if(what == ADB_CONNECT_FAIL){ setProgressMsg("与公司设备连接失败, 请确认IP与端口是否正确,再次点击重连。"); mainHandler.sendEmptyMessageDelayed(ADB_CONNECT_FAIL_HIDE,2000); } else if(what == ADB_CONNECT_FAIL_HIDE){ hideProgress(); } else if(what == SERVER_ENV_PREPARED){ setProgressMsg("公司设备初始化完成,即将投屏..."); // hideProgress(); start_screen_copy_magic(); } else if(what == GOT_REMOTE_VERSION){ hideProgress(); HartBeatMsgResp hartBeatMsgResp = (HartBeatMsgResp)message.obj; Toast.makeText(context, hartBeatMsgResp.remoteInfo, Toast.LENGTH_SHORT).show(); TextView tv_remote_device_info = findViewById(R.id.tv_remote_device_info); String remoteVer = String.format(getResources().getString(R.string.str_remote_dev_info),hartBeatMsgResp.remoteInfo); tv_remote_device_info.setText(remoteVer); } else if(what == GOT_REMOTE_LOG_PROCEDURE){ String str_save_log_to_local = (String)message.obj; TextView tv_save_log_to_local = findViewById(R.id.tv_save_log_to_local); tv_save_log_to_local.setText(str_save_log_to_local); } else if (what == GOT_REMOTE_LOG_PROCEDURE_END){ Boolean ok = (Boolean)message.obj; String str_remote_log_dlst = String.format(getResources().getString(R.string.str_remote_log_dlst),ok ? "成功":"失败"); TextView tv_remote_log_dlst = findViewById(R.id.tv_remote_log_dlst); tv_remote_log_dlst.setTextColor(ok ? Color.BLUE : Color.RED); tv_remote_log_dlst.setText(str_remote_log_dlst); } else if (what == GOT_REMOTE_LOG_PROCEDURE_START){ TextView tv_remote_log_dlst = findViewById(R.id.tv_remote_log_dlst); tv_remote_log_dlst.setTextColor(Color.GRAY); String str_remote_log_dlst = String.format(getResources().getString(R.string.str_remote_log_dlst),"下载中"); tv_remote_log_dlst.setText(str_remote_log_dlst); } return false; } }); private void initDownloadBtn(){ final Button btn_dl_status = findViewById(R.id.btn_dl_status); btn_dl_status.setOnClickListener(v -> { Message msg = new Message(); msg.what = GOT_REMOTE_LOG_PROCEDURE_START; mainHandler.sendMessage(msg); scrcpy.downloadLog(); }); final Button btn_dl_imgs = findViewById(R.id.btn_dl_imgs); btn_dl_imgs.setOnClickListener(v -> { Message msg = new Message(); msg.what = GOT_REMOTE_LOG_PROCEDURE_START; mainHandler.sendMessage(msg); scrcpy.downloadPics(); }); } private final ServiceConnection serviceConnection = new ServiceConnection() { @Override public void onServiceConnected(ComponentName componentName, IBinder iBinder) { scrcpy = ((Scrcpy.MyServiceBinder) iBinder).getService(); scrcpy.setServiceCallbacks(MainActivity.this); serviceBound = true; if (first_time) { scrcpy.start(surface, serverAdr, screenHeight, screenWidth); int count = 100; while (count!=0 && !scrcpy.check_socket_connection()){ count --; try { Thread.sleep(100); } catch (InterruptedException e) { e.printStackTrace(); } } if (count == 0){ if (serviceBound) { scrcpy.StopService(); unbindService(serviceConnection); serviceBound = false; scrcpy_main(); } Toast.makeText(context, "Connection Timed out", Toast.LENGTH_SHORT).show(); }else{ int[] rem_res = scrcpy.get_remote_device_resolution(); remote_device_height = rem_res[1]; remote_device_width = rem_res[0]; first_time = false; } } else { scrcpy.setParms(surface, screenWidth, screenHeight); } set_display_nd_touch(); } @Override public void onServiceDisconnected(ComponentName componentName) { serviceBound = false; } }; public MainActivity() { } @SuppressLint("SourceLockedOrientationActivity") @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); rxPermissions = new RxPermissions(this); rxPermissions .request(Manifest.permission.WRITE_EXTERNAL_STORAGE,Manifest.permission.INTERNET,Manifest.permission.ACCESS_WIFI_STATE) .subscribe(granted -> { if (granted) { Log.e("scrcpy","所有权限都被授予"); //performActionRequiringPermissions(); } else { Log.e("scrcpy","至少一个权限被拒绝"); showPermissionDeniedDialog(); } }); if (first_time) { scrcpy_main(); } else { this.context = this; start_screen_copy_magic(); } sensorManager = (SensorManager) this.getSystemService(SENSOR_SERVICE); Sensor proximity; proximity = sensorManager.getDefaultSensor(Sensor.TYPE_PROXIMITY); sensorManager.registerListener(this, proximity, SensorManager.SENSOR_DELAY_NORMAL); } @Override public void onOkIoMsg(BaseMsg baseMsg){ if(baseMsg instanceof LogFileMsgResp){ LogFileMsgResp logFileMsgResp = (LogFileMsgResp)baseMsg; String sizeFmt = ZipUtils.formetFileSize(logFileMsgResp.fileSize); String percentStr = logFileMsgResp.writenPercent+"%";//Math.round((logFileMsgResp.pktNo+1)*1.0f/logFileMsgResp.pktNums * 100)+"%"; String str_save_log_to_local = String.format(getResources().getString(R.string.str_save_log_to_local),logFileMsgResp.zipPath,sizeFmt,percentStr); Message msg = new Message(); msg.what = GOT_REMOTE_LOG_PROCEDURE; msg.obj = str_save_log_to_local; mainHandler.sendMessage(msg); } else if(baseMsg instanceof HartBeatMsgResp){ Message msg = new Message(); msg.what = GOT_REMOTE_VERSION; msg.obj = baseMsg; mainHandler.sendMessage(msg); } else if(baseMsg instanceof LogFileEndMsgResp){ LogFileEndMsgResp logFileEndMsgResp = (LogFileEndMsgResp)baseMsg; Message msg = new Message(); msg.what = GOT_REMOTE_LOG_PROCEDURE_END; msg.obj = logFileEndMsgResp.allDownloaded; mainHandler.sendMessage(msg); } } private void showProgress(){ Log.e("scrcpy","Asset showProgress"); mainProgressDialog = new ProgressDialog(context); mainProgressDialog.setTitle("提示"); mainProgressDialog.setMessage("正在连接公司设备..."); mainProgressDialog.setProgressStyle(ProgressDialog.STYLE_SPINNER); mainProgressDialog.setIndeterminate(true); mainProgressDialog.setCancelable(false); mainProgressDialog.setCanceledOnTouchOutside(false); mainProgressDialog.show(); } private void setProgressMsg(String msg){ mainProgressDialog.setMessage(msg); } private void hideProgress(){ if(mainProgressDialog.isShowing()){ mainProgressDialog.dismiss(); } } @SuppressLint("SourceLockedOrientationActivity") public void scrcpy_main(){ setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_PORTRAIT); setContentView(R.layout.activity_main); final Button startButton = findViewById(R.id.button_start); AssetManager assetManager = getAssets(); try { InputStream input_Stream = assetManager.open("scrcpy-server.jar"); byte[] buffer = new byte[input_Stream.available()]; input_Stream.read(buffer); fileBase64 = Base64.encode(buffer, 2); } catch (IOException e) { Log.e("Asset Manager", e.getMessage()); } sendCommands = new SendCommands(mainHandler); startButton.setOnClickListener(v -> { local_ip = wifiIpAddress(); getAttributes(); if (!serverAdr.isEmpty()) { //原生逻辑 // if (sendCommands.SendAdbCommands(context, fileBase64, serverAdr, Integer.parseInt(serverPort), local_ip, videoBitrate, Math.max(screenHeight, screenWidth)) == 0) { // start_screen_copy_magic(); // } else { // Toast.makeText(context, "Network OR ADB connection failed", Toast.LENGTH_SHORT).show(); // } //开始与adbd建立连接 if(first_time){ mainHandler.sendEmptyMessage(ADB_CONNECT_START); } } else { Toast.makeText(context, "Server Address Empty", Toast.LENGTH_SHORT).show(); } }); get_saved_preferences(); } public void get_saved_preferences(){ this.context = this; final EditText editTextServerHost = findViewById(R.id.editText_server_host); final Switch aSwitch0 = findViewById(R.id.switch0); final Switch aSwitch1 = findViewById(R.id.switch1); final EditText editTextServerPort = findViewById(R.id.editText_server_port); editTextServerHost.setText(context.getSharedPreferences(PREFERENCE_KEY, 0).getString("Server Address", "")); editTextServerPort.setText(context.getSharedPreferences(PREFERENCE_KEY, 0).getString("Server Port", "")); aSwitch0.setChecked(context.getSharedPreferences(PREFERENCE_KEY, 0).getBoolean("No Control", false)); aSwitch1.setChecked(context.getSharedPreferences(PREFERENCE_KEY, 0).getBoolean("Nav Switch", false)); setSpinner(R.array.options_resolution_keys, R.id.spinner_video_resolution, PREFERENCE_SPINNER_RESOLUTION); setSpinner(R.array.options_bitrate_keys, R.id.spinner_video_bitrate, PREFERENCE_SPINNER_BITRATE); if(aSwitch0.isChecked()){ aSwitch1.setVisibility(View.GONE); } aSwitch0.setOnClickListener(v -> { if(aSwitch0.isChecked()){ aSwitch1.setVisibility(View.GONE); }else{ aSwitch1.setVisibility(View.VISIBLE); } }); } @SuppressLint("ClickableViewAccessibility") public void set_display_nd_touch() { DisplayMetrics metrics = new DisplayMetrics(); if (ViewConfiguration.get(context).hasPermanentMenuKey()) { getWindowManager().getDefaultDisplay().getMetrics(metrics); } else { final Display display = getWindowManager().getDefaultDisplay(); display.getRealMetrics(metrics); } float this_dev_height = metrics.heightPixels; float this_dev_width = metrics.widthPixels; if (nav && !no_control){ if (landscape){ this_dev_width = this_dev_width - 96; }else { //100 is the height of nav bar but need multiples of 8. this_dev_height = this_dev_height - 96; } } float remote_device_aspect_ratio = remote_device_height/remote_device_width; //改变渲染的边距 if (!landscape) { //Portrait float this_device_aspect_ratio = this_dev_height/this_dev_width; if (remote_device_aspect_ratio > this_device_aspect_ratio) { linearLayout.setPadding((int) (((remote_device_aspect_ratio - this_device_aspect_ratio)*this_dev_width)/2),0,(int) (((remote_device_aspect_ratio - this_device_aspect_ratio)*this_dev_width)/2),0); } else if (remote_device_aspect_ratio < this_device_aspect_ratio) { linearLayout.setPadding(0,(int) (((this_device_aspect_ratio - remote_device_aspect_ratio)*this_dev_width)),0,0); } }else{ //Landscape float this_device_aspect_ratio = this_dev_width/this_dev_height; if (remote_device_aspect_ratio > this_device_aspect_ratio) { linearLayout.setPadding(0,(int) (((remote_device_aspect_ratio - this_device_aspect_ratio)*this_dev_height)/2),0,(int) (((remote_device_aspect_ratio - this_device_aspect_ratio)*this_dev_height)/2)); } else if (remote_device_aspect_ratio < this_device_aspect_ratio) { linearLayout.setPadding(((int) (((this_device_aspect_ratio - remote_device_aspect_ratio)*this_dev_height))/2),0,((int) (((this_device_aspect_ratio - remote_device_aspect_ratio)*this_dev_height))/2),0); } } if (!no_control) { surfaceView.setOnTouchListener((v, event) -> scrcpy.touchevent(event, surfaceView.getWidth(), surfaceView.getHeight())); } if (nav && !no_control) { final Button backButton = findViewById(R.id.back_button); final Button homeButton = findViewById(R.id.home_button); final Button appswitchButton = findViewById(R.id.appswitch_button); backButton.setOnClickListener(v -> scrcpy.sendKeyevent(4)); homeButton.setOnClickListener(v -> scrcpy.sendKeyevent(3)); appswitchButton.setOnClickListener(v -> scrcpy.sendKeyevent(187)); } } private void setSpinner(final int textArrayOptionResId, final int textViewResId, final String preferenceId) { final Spinner spinner = findViewById(textViewResId); ArrayAdapter arrayAdapter = ArrayAdapter.createFromResource(this, textArrayOptionResId, android.R.layout.simple_spinner_item); arrayAdapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item); spinner.setAdapter(arrayAdapter); spinner.setOnItemSelectedListener(new AdapterView.OnItemSelectedListener() { @Override public void onItemSelected(AdapterView parent, View view, int position, long id) { context.getSharedPreferences(PREFERENCE_KEY, 0).edit().putInt(preferenceId, position).apply(); } @Override public void onNothingSelected(AdapterView parent) { context.getSharedPreferences(PREFERENCE_KEY, 0).edit().putInt(preferenceId, 0).apply(); } }); spinner.setSelection(context.getSharedPreferences(PREFERENCE_KEY, 0).getInt(preferenceId, 0)); } private void getAttributes() { final EditText editTextServerHost = findViewById(R.id.editText_server_host); serverAdr = editTextServerHost.getText().toString(); final EditText editTextServerPort = findViewById(R.id.editText_server_port); serverPort = editTextServerPort.getText().toString(); context.getSharedPreferences(PREFERENCE_KEY, 0).edit().putString("Server Port", serverPort).apply(); context.getSharedPreferences(PREFERENCE_KEY, 0).edit().putString("Server Address", serverAdr).apply(); final Spinner videoResolutionSpinner = findViewById(R.id.spinner_video_resolution); final Spinner videoBitrateSpinner = findViewById(R.id.spinner_video_bitrate); final Switch a_Switch0 = findViewById(R.id.switch0); no_control = a_Switch0.isChecked(); final Switch a_Switch1 = findViewById(R.id.switch1); nav = a_Switch1.isChecked(); context.getSharedPreferences(PREFERENCE_KEY, 0).edit().putBoolean("No Control", no_control).apply(); context.getSharedPreferences(PREFERENCE_KEY, 0).edit().putBoolean("Nav Switch", nav).apply(); final String[] videoResolutions = getResources().getStringArray(R.array.options_resolution_values)[videoResolutionSpinner.getSelectedItemPosition()].split(","); screenHeight = Integer.parseInt(videoResolutions[0]); screenWidth = Integer.parseInt(videoResolutions[1]); videoBitrate = getResources().getIntArray(R.array.options_bitrate_values)[videoBitrateSpinner.getSelectedItemPosition()]; } private void swapDimensions() { int temp = screenHeight; screenHeight = screenWidth; screenWidth = temp; } @SuppressLint("ClickableViewAccessibility") private void start_screen_copy_magic() { Log.e("Scrcpy: ","Starting scrcpy service"); setContentView(R.layout.surface);//开始渲染 final View decorView = getWindow().getDecorView(); decorView.setSystemUiVisibility( 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 | View.SYSTEM_UI_FLAG_IMMERSIVE_STICKY); surfaceView = findViewById(R.id.decoder_surface); surface = surfaceView.getHolder().getSurface(); final LinearLayout nav_bar = findViewById(R.id.nav_button_bar); if(nav && !no_control) { nav_bar.setVisibility(LinearLayout.VISIBLE); }else { nav_bar.setVisibility(LinearLayout.GONE); } linearLayout = findViewById(R.id.container1); start_Scrcpy_service(); initDownloadBtn();//loadNewRotation 屏幕旋转需要重新初始化 } protected String wifiIpAddress() { //https://stackoverflow.com/questions/6064510/how-to-get-ip-address-of-the-device-from-code try { InetAddress ipv4 = null; InetAddress ipv6 = null; for (Enumeration en = NetworkInterface .getNetworkInterfaces(); en.hasMoreElements(); ) { NetworkInterface int_f = en.nextElement(); for (Enumeration enumIpAddr = int_f .getInetAddresses(); enumIpAddr.hasMoreElements(); ) { InetAddress inetAddress = enumIpAddr.nextElement(); if (inetAddress instanceof Inet6Address) { ipv6 = inetAddress; continue; } if (inetAddress.isLoopbackAddress() && inetAddress instanceof Inet4Address) { ipv4 = inetAddress; continue; } return inetAddress.getHostAddress(); } } if (ipv6 != null) { return ipv6.getHostAddress(); } if (ipv4 != null) { return ipv4.getHostAddress(); } return null; } catch (SocketException ex) { ex.printStackTrace(); } return null; } private void start_Scrcpy_service() { Intent intent = new Intent(this, Scrcpy.class); startService(intent); bindService(intent, serviceConnection, Context.BIND_AUTO_CREATE); } @SuppressLint("SourceLockedOrientationActivity") @Override public void loadNewRotation() { Log.e("Scrcpy: ","loadNewRotation "+first_time); if (first_time){ int[] rem_res = scrcpy.get_remote_device_resolution(); remote_device_height = rem_res[1]; remote_device_width = rem_res[0]; first_time = false; } unbindService(serviceConnection); serviceBound = false; result_of_Rotation = true; landscape = !landscape; swapDimensions(); if (landscape) { setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE); } else { setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_PORTRAIT); } } @Override protected void onPause() { super.onPause(); if (serviceBound) { scrcpy.pause(); } } @Override protected void onResume() { super.onResume(); if (!first_time && !result_of_Rotation) { final View decorView = getWindow().getDecorView(); decorView.setSystemUiVisibility( 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 | View.SYSTEM_UI_FLAG_IMMERSIVE_STICKY); if (serviceBound) { linearLayout = findViewById(R.id.container1); scrcpy.resume(); } } result_of_Rotation = false; } @Override public void onBackPressed() { if (timestamp == 0) { timestamp = SystemClock.uptimeMillis(); Toast.makeText(context, "Press again to exit", Toast.LENGTH_SHORT).show(); } else { long now = SystemClock.uptimeMillis(); if (now < timestamp + 1000) { timestamp = 0; if (serviceBound) { scrcpy.StopService(); unbindService(serviceConnection); } android.os.Process.killProcess(android.os.Process.myPid()); System.exit(1); } timestamp = 0; } } @Override public void onSensorChanged(SensorEvent sensorEvent) { if (sensorEvent.sensor.getType() == Sensor.TYPE_PROXIMITY) { if (sensorEvent.values[0] == 0) { if (serviceBound) { scrcpy.sendKeyevent(28); } } else { if (serviceBound) { scrcpy.sendKeyevent(29); } } } } @Override public void onAccuracyChanged(Sensor sensor, int i) { } private void showPermissionDeniedDialog(){ AlertDialog.Builder builder = new AlertDialog.Builder(this); builder.setMessage("请前往设置界面打开SD卡读写权限"); builder.setPositiveButton("去设置", new DialogInterface.OnClickListener() { @Override public void onClick(DialogInterface dialogInterface, int i) { Intent intent = new Intent(Settings.ACTION_APPLICATION_DETAILS_SETTINGS, Uri.parse("package:"+getPackageName())); intent.addCategory(Intent.CATEGORY_DEFAULT); intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK); startActivity(intent); } }); builder.setNegativeButton("取消",new DialogInterface.OnClickListener() { @Override public void onClick(DialogInterface dialogInterface, int i) { finish(); } }); builder.show(); } }