package com.javispedro.vndroid; import android.app.Activity; import android.content.ComponentName; import android.content.Intent; import android.content.ServiceConnection; import android.media.projection.MediaProjectionManager; import android.os.Bundle; import android.os.IBinder; import android.util.Log; import android.widget.Toast; import androidx.annotation.Nullable; import androidx.appcompat.app.AppCompatActivity; import androidx.preference.Preference; import androidx.preference.PreferenceFragmentCompat; import androidx.preference.TwoStatePreference; import java.util.List; public class SettingsActivity extends AppCompatActivity { private static final String TAG = SettingsActivity.class.getSimpleName(); private static final int REQUEST_MEDIA_PROJECTION = 1; private static final boolean mirrorScreenMode = true; public static class SettingsFragment extends PreferenceFragmentCompat implements ServerService.ServerStatusCallback { private ServerConnection serverConnection = null; @Override public void onCreate(@Nullable Bundle savedInstanceState) { super.onCreate(savedInstanceState); } @Override public void onCreatePreferences(Bundle savedInstanceState, String rootKey) { setPreferencesFromResource(R.xml.root_preferences, rootKey); findPreference(getString(R.string.settings_enable_key)).setOnPreferenceClickListener(new Preference.OnPreferenceClickListener() { @Override public boolean onPreferenceClick(Preference preference) { TwoStatePreference pref = (TwoStatePreference) preference; setServerEnabled(pref.isChecked()); return true; } }); } @Override public void onStart() { super.onStart(); serverConnection = new ServerConnection(); serverConnection.bind(requireActivity(), this); } @Override public void onStop() { serverConnection.close(requireActivity()); serverConnection = null; super.onStop(); } @Override public void onActivityResult(int requestCode, int resultCode, Intent data) { switch (requestCode) { case REQUEST_MEDIA_PROJECTION: notifyMediaProjectionResult(resultCode, data); return; } super.onActivityResult(requestCode, resultCode, data); } @Override public void onServerStatusChanged() { postUpdateServerStatus(); } @Override public void onNumClientChanged() { postUpdateServerStatus(); } private void setServerEnabled(boolean state) { ServerService server = serverConnection.getServer(); if (state) { if (server == null) { throw new IllegalStateException("ServerService not bound"); } if (server.isServerActive()) return; if (mirrorScreenMode) { MediaProjectionManager manager = requireContext().getSystemService(MediaProjectionManager.class); startActivityForResult(manager.createScreenCaptureIntent(), REQUEST_MEDIA_PROJECTION); } else { server.startServer(); } } else { if (server == null) return; server.stopServer(); } } private void notifyMediaProjectionResult(int resultCode, Intent resultData) { if (resultCode != Activity.RESULT_OK) { Log.w(TAG, "User cancelled media projection"); Toast.makeText(requireContext(), getString(R.string.toast_no_mirror_permission), Toast.LENGTH_SHORT).show(); updateServerStatus(); return; } ServerService server = serverConnection.getServer(); if (server == null) { Log.e(TAG, "server died before projection could be sent"); updateServerStatus(); return; } if (server.isServerActive()) { Log.w(TAG, "server already active"); updateServerStatus(); return; } server.setMediaProjectionResult(resultCode, resultData); server.startServer(); } private void updateServerStatus() { TwoStatePreference enablePref = findPreference(getString(R.string.settings_enable_key)); Preference statusPref = findPreference(getString(R.string.settings_status_key)); ServerService server = serverConnection.getServer(); if (server != null && server.isServerActive()) { enablePref.setChecked(true); StringBuilder sb = new StringBuilder(); int display = server.getListeningDisplay(); List ips = server.getListeningIPAddresses(); if (!ips.isEmpty()) { sb.append('\n'); sb.append(getString(R.string.status_server_addresses)); for (String ip : ips) { sb.append(' '); sb.append(ip); sb.append(':'); sb.append(display); } } int numClients = server.getNumClients(); if (numClients > 0) { sb.append('\n'); sb.append(getResources().getQuantityString(R.plurals.status_num_clients, numClients, numClients)); } statusPref.setSummary(sb.toString()); } else { enablePref.setChecked(false); statusPref.setSummary(""); } } private void postUpdateServerStatus() { requireActivity().runOnUiThread(new Runnable() { public void run() { updateServerStatus(); } }); } } @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.settings_activity); getSupportFragmentManager() .beginTransaction() .replace(R.id.settings, new SettingsFragment()) .commit(); } private static class ServerConnection implements ServiceConnection { private ServerService server = null; private ServerService.ServerStatusCallback callback = null; public void bind(Activity activity, ServerService.ServerStatusCallback callback) { this.callback = callback; Intent intent = new Intent(activity, ServerService.class); intent.setAction(ServerService.ACTION_INIT_SERVICE); activity.startService(intent); activity.bindService(intent, this, 0); } public void close(Activity activity) { if (server != null) { server.setServerStatusCallback(null); server = null; } activity.unbindService(this); callback = null; } public boolean connected() { return server != null; } public @Nullable ServerService getServer() { return server; } @Override public void onServiceConnected(ComponentName name, IBinder service) { server = ((ServerService.ServerBinder) service).getService(); server.setServerStatusCallback(this.callback); if (this.callback != null) { callback.onServerStatusChanged(); } } @Override public void onServiceDisconnected(ComponentName name) { server = null; } } }