Differences

This shows you the differences between the selected revisions of the page.

2022-09-12 2022-09-12
no summary (188.43.235.177) (hidden) (untrusted) no summary (188.43.235.177) (hidden) (untrusted)
Line 1: Line 1:
-====== ====== Recent Version History ======+/* 
 + * Headwind Remote: Open Source Remote Access Software for Android 
 + * https://headwind-remote.com 
 + * 
 + * Copyright (C) 2022 headwind-remote.com 
 +
 + * Licensed under the Apache License, Version 2.0 (the "License"); 
 + * you may not use this file except in compliance with the License. 
 + * You may obtain a copy of the License at 
 +
 + *      http://www.apache.org/licenses/LICENSE-2.0 
 +
 + * Unless required by applicable law or agreed to in writing, software 
 + * distributed under the License is distributed on an "AS IS" BASIS, 
 + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 
 + * See the License for the specific language governing permissions and 
 + * limitations under the License. 
 + */
-This is a full list of changes for each release of WinSCP. See also [[project_history|Project history]] and [[incompatible_changes|Incompatible changes between versions]]. +package com.hmdm.control;
- ======+
-·······················+import android.app.AlertDialog; 
 +import android.app.Dialog; 
 +import android.content.BroadcastReceiver; 
 +import android.content.ClipData; 
 +import android.content.ClipboardManager; 
 +import android.content.Context; 
 +import android.content.DialogInterface; 
 +import android.content.Intent; 
 +import android.content.IntentFilter; 
 +import android.graphics.PixelFormat; 
 +import android.media.projection.MediaProjectionManager; 
 +import android.os.Build; 
 +import android.os.Bundle; 
 +import android.os.Handler; 
 +import android.util.DisplayMetrics; 
 +import android.util.Log; 
 +import android.view.Gravity; 
 +import android.view.Menu; 
 +import android.view.MenuItem; 
 +import android.view.View; 
 +import android.view.WindowManager; 
 +import android.widget.EditText; 
 +import android.widget.ImageView; 
 +import android.widget.TextView; 
 +import android.widget.Toast;
-··+import androidx.appcompat.app.AppCompatActivity; 
 +import androidx.appcompat.widget.Toolbar; 
 +import androidx.localbroadcastmanager.content.LocalBroadcastManager;
-··+import com.hmdm.control.janus.SharingEngineJanus;
-··+public class MainActivity extends AppCompatActivity implements SharingEngineJanus.EventListener, SharingEngineJanus.StateListener {
-··+    private ImageView imageViewConnStatus; 
 +    private TextView textViewConnStatus; 
 +    private EditText editTextSessionId; 
 +    private EditText editTextPassword; 
 +    private TextView textViewComment; 
 +    private TextView textViewConnect; 
 +    private TextView textViewSendLink; 
 +    private TextView textViewExit;
-====== ===== [[5.21]] 5.21 ((2022-06-15)) =====+····private ImageView overlayDot; 
 +····private Handler handler = new Handler(); 
 + ···private int overlayDotAlpha; 
 + ···private int overlayDotDirection = 1;
-··* Bug fix: Hang when entering small directories with FTP protocol. [[bug>;2087]] +····private Dialog exitOnIdleDialog
- ·* Bug fix: Lag when moving files from remote panel using drag&;drop. [[bug>2088]] + ···private int exitCounter
- ·* Bug fix: After certain operations, drive that was ever opened in local file panel cannot be safely removed temporarily+ ···private static final int EXIT_PROMPT_SEC = 10;
- ======+
-===== [[5.20.4]] 5.20.4 RC ((2022-06-08)) ===== +····private static final int OVERLAY_DOT_ANIMATION_INCREMENT = 20
-·  +    private static final int OVERLAY_DOT_ANIMATION_DELAY = 200;
-  * SSH core and SSH private key tools (PuTTYgen and Pageant) upgraded to [[&;url(puttychanges)|PuTTY 0.77]]. It brings the following changes: +
-    * Pageant: Option ''%%--openssh-config%%'' to allow easy interoperation with Windows's ''ssh.exe''. [[pbug>;win-pageant-openssh-interop]] +
-    * Bug fix: PuTTYgen's mouse-based entropy collection now handles high-frequency mice without getting confused. [[pbug>win-puttygen-entropy-rate-limit]] +
-    * Bug fix: Pageant can now handle large numbers of concurrent connections without hanging or crashing. [[pbug>win-pageant-max-connections-2]] +
-    * Bug fix: If Pageant is started multiple times simultaneously, the instances should reliably agree on one of them to be the persistent server. [[pbug>win-pageant-concurrent-startup]] +
-  * Improved error message when FTP server returns malformed response. [[bug>2077]] +
-  * Translations updated: German and Slovak. +
-  * Not listing remote directory when downloading file using FTP protocol with overwrite confirmations off. [[bug>2084]] +
-  * Workaround for calls to system API failing when used first time against some network shares (Samba) with paths over the legacy Windows limit. [[bug>2082]] +
-  * Workaround for an apparent bug in Windows 11 that prevents WinSCP from stopping Windows going to the sleep mode during transfers. [[bug>2083]] +
-  * Workaround for specific encoding of commas in filenames (and particularly directory names) by OneDrive WebDAV interface. [[bug>2085]] +
-  * Bug fix: When transferring a growing file, after its original size is reached, the ''Session.FileTransferProgress'' event starts being triggered continuously. [[bug>2078]] +
-  * Bug fix: Restored pre-5.20.3 behaviour with MVS systems. [[bug>2086]]+
-===== [[5.20.3]] 5.20.3 RC ((2022-05-17)) =====+····private SharingEngine sharingEngine;
-··* Translations completed: Catalan, Czech, Dutch, Finnish, French, German, Hungarian, Italian, Japanese, Korean, Portuguese, Romanian, Russian, Simplified Chinese, Slovak, Spanish, Swedish, Traditional Chinese, Turkish and Ukrainian; and updated: Vietnamese +····private SettingsHelper settingsHelper;
-  * Streaming support in .NET assembly and scripting for FTP protocol. [[bug>1945]] +
-  * Improved compatibility with MVS systems. [[bug>2069]] +
-  * New .NET assembly method ''Session.TryGetFileInfo''. Thanks to @RachamimYaakobov. [[bug>2068]] +
-  * Expanding environment variables in Open Directory/Location Profiles dialogs in local paths. [[bug>909]] +
-  * Optimized loading of large directories. [[bug>1631]] +
-  * TLS/SSL core upgraded to OpenSSL 1.1.1o. +
-  * Installer upgraded to Inno Setup 6.2.1. +
-  * When group (//Site/Shared//) to which new location profile is added is changed, switching the page to the target group. +
-  * Preventing occasional exhaustion of resources while testing WinSCP executable version on repeated use of .NET assembly. [[bug>2075]] +
-  * Bug fix: Capabilities of S3 sessions were not shown. +
-  * Bug fix: Misplaced warning about unused scripting parameters when ''/rawsettings'' command-line switch is used. [[bug>2070]] +
-  * Bug fix: File panels malfunction when files are dropped from other application to it. [[bug>2071]] +
-  * Bug fix: When logging transfer statistics, recent transfer speed was logged instead of average speed of whole transfer. [[bug>2073]] +
-  * Bug fix: z/OS PDS members without ISPF statistics are omitted in directory listing. [[bug>2076]]+
-===== [[5.20.2]] 5.20.2 beta ((2022-04-06)) =====+····private String sessionId; 
 + ···private String password; 
 +····private String adminName;
-··* Translations updated: German and Vietnamese. +····private final static String ATTR_SESSION_ID = "sessionId"; 
-  * SSH core upgraded to pre-release build of [[https://www.chiark.greenend.org.uk/~sgtatham/putty/wishlist/|PuTTY 0.77]]. (*TODO replace with &url(puttychanges) once 0.77 is released*) \\ It brings the following changes: +    private final static String ATTR_PASSWORD = "password"; 
-    * Support for HTTP Digest authentication for proxies. [[pbug>http-digestauth]] + ···private final static String ATTR_ADMIN_NAME = "adminName";
-    * Interactive username/password prompts for proxy authentication. [[bug>468]] [[pbug>proxy-password-prompt]] +
-  * TLS/SSL core upgraded to OpenSSL 1.1.1n. +
-  * WebDAV core upgraded to neon 0.32.2. +
-  * XML parser upgraded to Expat 2.4.8. +
-  * Allowed uploading edited files over encrypted session. [[bug>1989]] +
-  * Building .NET assembly in Visual Studio 2019. +
-  * Optionally not flashing taskbar button when WinSCP needs attention while in the background. [[bug>2067]] +
-  * In .NET assembly or scripting with "nul"; configuration, when WinSCP registry key does not exist, do not write statistics to INI file. [[bug>2066]] +
- ·* New less prominent icon for //Quit// command. +
-  * Bug fix: Failure when attempting to switch to an unconnected sessions using keyboard shortcuts while another session is being connected. +
-  * Bug fix: Removing all custom commands resets the default ones when INI file is used. [[bug>2059]] +
-  * Bug fix: Failure when changing remote folder using directory tree while previous directory is still loading. [[bug>2060]] +
-  * Bug fix: Drive that was ever opened in local file panel cannot be safely removed until WinSCP is closed. [[bug>2061]] +
-  * Bug fix: //"Optimize connection buffer size"// checkbox was disabled for S3 although it has effect for the protocol. [[bug>;2058]] +
-  * Bug fix: Custom commands menu fail to open. [[bug>2062]] +
-  * Bug fix: Local drive menu does not reflect some changes. [[bug>2063]] +
-  * Bug fix: Using drive menu to change to an invalid drive fails silently. +
-  * Bug fix: Scripting command ''ls'' in FTP session displays timestamps without year even if the server (notably IIS) provided the year, if it did not provide seconds. [[bug>2065]]+
-===== [[5.20.1]] 5.20.1 beta ((2022-01-11)) ===== +····private boolean needReconnect = false;
-· * File masks relative to the root of an operation. [[bug>;2052]] +
-  * Private key can be provided as string in .NET assembly and scripting. [[bug>2044]] +
-  * Support for importing key files that are specified using home ''~'' prefix from OpenSSH ''config'' file. [[bug>2053]] +
-  * Translation updated: German. +
-  * TLS/SSL core upgraded to OpenSSL 1.1.1m. +
-  * Commands that do not fit on //Custom Commands// toolbar are presented in drop down menu, instead of a horizontal bar. [[bug>554]] +
-  * First hiding right-most //Custom Commands// toolbar commands that do not fit. +
-  * Prevent hang when dragging files without drag&drop shell extensions when some mapped network drive is not available. [[bug>2054]] +
-  * Added new ''ap-southeast-3'' AWS region. +
-  * XML parser upgraded to Expat 2.4.2. +
-  * Bug fix: When initial remote directory specified in the ''open'' command does not exist, the error was silently ignored. +
-  * Bug fix: File color rules with path mask do not work for local files.+
-===== [[5.20]] 5.20 beta ((2021-12-02)) =====+····private MediaProjectionManager projectionManager;
-··* Support for ACL for S3 protocol. [[bug>1641]] +····private BroadcastReceiver mSharingServiceReceiver = new BroadcastReceiver() { 
-  * SSH core upgraded to [[&url(puttychanges)|PuTTY 0.76]]. [[bug>1984]] \\ It brings the following changes: + ·······@Override 
- ···* Support Curve448 key exchange method. [[pbug>curve448]] + ·······public void onReceive(Context context, Intent intent) { 
- ···* Support Ed448 user and host keys. [[pbug>ed448]] + ···········if (intent == null || intent.getAction() == null) { 
-    * Support rsa-sha2-256 and rsa-sha2-512 SSH public key algorithms. [[bug>1952]] [[pbug>rsa-sha2]] + ···············return
-    * Change: SHA-256 fingerprints are not padded anymore. + ···········} 
-  * It is possible to import sessions from OpenSSH ''config'' file. [[bug>1896]] + ···········if (intent.getAction().equals(Const.ACTION_SCREEN_SHARING_START)) { 
-  * Change: Removed support for SSH-1. + ···············notifySharingStart();
-  * Optionally keeping symbolic link name in path instead of resolving it with SFTP protocol. [[bug>81]] +
-  * Improved shortening long paths, when single level name is too long on its own. [[bug>2043]] +
- ·* WebDAV core upgraded to neon 0.32.1. +
-· * Allow displaying all VMS file revisions with FTP protocol. [[bug>1944]] +
-  * Support reading S3 credentials from AWS CLI configuration. [[bug>1941]] +
-· * Bytes transferred are recorded in XML log and available in .NET assembly API. [[bug>597]] +
- * Generating PPK3 keys. +
-  * It is possible to copy information to the clipboard from the Properties dialog. +
-  * Made ''WinSCP.exe'' be deployed even when WinSCP NuGet package is included as transitive dependency. [[bug>2017]] +
- ·* When performing a single-item atomic operation, displaying indeterminate progress status. +
- ·* S3 //Security token// session option renamed to a more appropriate //Session Token//. +
-  * Not resetting protocol to WebDAV when S3 protocol is selected and HTTP URL is pasted on the Login dialog. [[bug>2045]] +
-  * Automatically reconnect when FTP server fails to open data connection, if it previously worked. [[bug>2046]] +
-  * Confirm closing when multiple tabs are opened and auto workspace saving is not enabled, even when none of the tabs contain an active remote session. +
-··* Including host key of tunnel session in generated code. [[bug>2006]] +
-  * Support for custom certificate store files with configurable path. [[bug>2034]] +
-  * Allowed providing tunnel private key passphrase in scripting. [[bug>2029]] +
-  * Improved handling of long shell command error messages. [[bug>1949]] +
-  * Considering global passive/active mode settings when importing sessions from FileZilla +
-  * Automatically retry when S3 transfer fails with ''503'' code (//Slow down// or //Service unavailable//). [[bug>2047]] +
-  * Improving formatting of errors displayed after an operation completed in //Continue on error// mode. +
-··* ''Shift+Ctrl+R'' keyboard shortcut for //Reconnect Session// command. +
-  * Change: Keyboard shortcut for //Restore Selection// command changed to ''Shift+Ctrl+S'' (''Shift+Ctrl+R'' previously)+
- * Change: Monitoring ''A:'' and ''B:'' drives. [[bug>2014]] +
- ·* Preventing beep when using ''Alt+Enter'' to open the Properties dialog. +
-  * When collecting list of files for background transfer, say //"Listing"// instead of //"Calculating"// not to give an impression that it is a superfluous operation. [[bug>2026]] +
-  * Checking for too many parameters with ''/keygen'' switch. +
-  * PNG code upgraded to PngComponents 1.7.0. +
-  * Warning when selecting too new key file. +
-  * Warning when opening more than 100 tabs. [[bug>1997]] +
-  * Including HTTP message into S3 error message. +
-  * Bug fix: Do not say //"Terminated by user"// when the session has actually timed out. +
-  * Bug fix: Cannot use passwords and passphrases longer than 255 characters in automation and various other purposes. [[bug>1988]] +
-  * Bug fix: When size of a file downloaded with FTP protocol changes (or when ASCII mode is used) the logged size did not reflect the actual transfer size. +
-  * Bug fix: When connecting disconnected session the directory might be loaded twice. +
-  * Bug fix: Failure when entering directory that contains file with a slash in its name. [[bug>;2016]] +
-  * Bug fix: When S3 authentication region changes during session, previously visited buckets from the original authentication region could not be accessed anymore. [[bug>2027]] +
-  * Bug fix: Failure when using multiple connections with local proxy command. [[bug>2028]] +
-  * Bug fix: Configuration import  did not remove old sites and did not load all GUI settings. [[bug>2040]] +
-  * Bug fix: When S3 transfer fatally fails, error details were lost.+
-===== [[5.19.6]] 5.19.6 (hotfix) ((2022-02-22)) =====+············} else if (intent.getAction().equals(Const.ACTION_SCREEN_SHARING_STOP)) { 
 +················notifySharingStop(); 
 + ···············adminName = null
 +               updateUI(); 
 +················cancelSharingTimeout()
 + ···············scheduleExitOnIdle();
-··* Translation updated: German. +············} else if (intent.getAction().equals(Const.ACTION_SCREEN_SHARING_FAILED)) { 
-· * Back-propagated fixes from 5.20–5.20.2 releases: + ···············String message = intent.getStringExtra(Const.EXTRA_MESSAGE); 
- ···* TLS/SSL core upgraded to OpenSSL 1.1.1m. + ···············if (message != null)
- ···* XML parser upgraded to Expat 2.4.6.+                   Toast.makeText(MainActivity.this, message, Toast.LENGTH_LONG).show(); 
 + ···············
 +               adminName = null; 
 +               updateUI(); 
 + ···············cancelSharingTimeout(); 
 +                scheduleExitOnIdle();
-===== [[5.19.5]] 5.19.5 ((2021-11-25)) =====+············} else if (intent.getAction().equals(Const.ACTION_CONNECTION_FAILURE))
 +                sharingEngine.setState(Const.STATE_DISCONNECTED); 
 +               Toast.makeText(MainActivity.this, R.string.connection_failure_hint, Toast.LENGTH_LONG).show()
 + ···············updateUI();
-··* Compatibility with Google Cloud S3 API when duplicating files. [[bug>2038]] +············} else if (intent.getAction().equals(Const.ACTION_SCREEN_SHARING_PERMISSION_NEEDED)) { 
-  * Compatibility with Google Cloud S3 API when deleting implicitly existing directories. [[bug>2042]] + ···············startActivityForResult(projectionManager.createScreenCaptureIntent(), Const.REQUEST_SCREEN_SHARE)
-· * Translation updated: Turkish. + ···········} 
- ·* Logging a reference to [[bug>1952]] when an OpenSSH 8.8 (or newer) server refuses the key+ ·······} 
-· * Bug fix: Crash when new contents is copied to the clipboard while downloading files pasted from the clipboard. [[bug>;2036]] + ···};
- ·* Bug fix: //Browse Remote Directory// command on Synchronization checklist did not locate file with spaces. +
- ·* Bug fix: Extension //ZIP and Upload// does not work with files in a drive root. [[bug>;2039]] +
-  * Bug fix: Path on Console window was not shortened when it did not fit.+
-===== [[5.19.4]] 5.19.4 ((2021-10-24)) =====+····@Override 
 +····protected void onCreate(Bundle savedInstanceState) { 
 +········super.onCreate(savedInstanceState); 
 +········setContentView(R.layout.activity_main);
-··* Translation updated: Hungarian+········if (savedInstanceState != null) { 
-· * Showing release date on the About dialog. + ···········restoreInstanceState(savedInstanceState)
- ·* Support for custom certificate store files. [[bug>;2034]] + ·······}
- * Allow other ''2xx'' responses to ''PWD'' command, not only the standard ''257''. [[bug>1768]] +
-  * Bug fix: When there are both site folder and site with the same name and the site was selected when closing the Login dialog, when reopening, the folder was selected instead.+
-===== [[5.19.3]] 5.19.3 ((2021-10-11)) =====+········Toolbar toolbar = findViewById(R.id.toolbar)
 + ·······setSupportActionBar(toolbar); 
 +       settingsHelper = SettingsHelper.getInstance(this); 
 +········sharingEngine = SharingEngineFactory.getSharingEngine()
 +        sharingEngine.setEventListener(this)
 + ·······sharingEngine.setStateListener(this);
-··* Translation updated: French+········DisplayMetrics metrics = new DisplayMetrics(); 
-  * TLS/SSL core upgraded to OpenSSL 1.1.1l+       ScreenSharingHelper.getRealScreenSize(this, metrics); 
-  * Using //Documents// folder when the last used local directory in Explorer interface does not exist anymore. [[bug>2011]] +       float videoScale = ScreenSharingHelper.adjustScreenMetrics(metrics); 
-  * Bug fix: TLS session resumption is not working for subsequent FTP transfers with TLS 1.3 when the server requires reuse of the session of the previous transfer. [[bug>2018]] +       settingsHelper.setFloat(SettingsHelper.KEY_VIDEO_SCALE, videoScale); 
-  * Bug fix: Cannot access S3 bucket root when the access policy checks for empty prefix. [[bug>2021]] +        ScreenSharingHelper.setScreenMetrics(this, metrics.widthPixels, metrics.heightPixels, metrics.densityDpi); 
-  * Bug fix: Response from ProFTPD FTP checksum commands is not recognized. [[bug>2023]] + 
-  * Bug fix: Failure when submitting prompt with //"Never ask me again"// selected. [[bug>2022]] +        sharingEngine.setScreenWidth(metrics.widthPixels); 
-  * Bug fix: Panels are drawn incorrectly after toggling //Full row select//. [[bug>2025]] +        sharingEngine.setScreenHeight(metrics.heightPixels); 
-  * Bug fix: Timeout while uploading files to some FTP servers using TLS 1.3. [[bug>2030]] + 
-  * Bug fix: Incomplete listing for S3 servers that indicate truncated listing after the contents and whose pagination is a multiple of 8 (e.g. Backblaze). [[bug>2032]]+        IntentFilter intentFilter = new IntentFilter(Const.ACTION_SCREEN_SHARING_START); 
 +        intentFilter.addAction(Const.ACTION_SCREEN_SHARING_STOP); 
 +        intentFilter.addAction(Const.ACTION_SCREEN_SHARING_PERMISSION_NEEDED); 
 +        intentFilter.addAction(Const.ACTION_SCREEN_SHARING_FAILED); 
 +        intentFilter.addAction(Const.ACTION_CONNECTION_FAILURE); 
 +        LocalBroadcastManager.getInstance(this).registerReceiver(mSharingServiceReceiver, intentFilter); 
 + 
 +        projectionManager = (MediaProjectionManager)getSystemService(Context.MEDIA_PROJECTION_SERVICE); 
 + 
 +        initUI(); 
 +        setDefaultSettings(); 
 +    } 
 + 
 +    @Override 
 +    protected void onResume() { 
 +        super.onResume(); 
 +        updateUI(); 
 + 
 +        startService(new Intent(MainActivity.this, GestureDispatchService.class)); 
 +//       connect(); 
 +       checkAccessibility(); 
 +   } 
 + 
 +   private void checkAccessibility() { 
 +        if (!Utils.isAccessibilityPermissionGranted(this)) { 
 +            textViewConnect.setVisibility(View.INVISIBLE); 
 +           new AlertDialog.Builder(this) 
 +                   .setMessage(R.string.accessibility_hint) 
 +                   .setPositiveButton(R.string.continue_button, new DialogInterface.OnClickListener() { 
 +                        @Override 
 +                        public void onClick(DialogInterface dialog, int which) { 
 +                            Intent intent = new Intent(android.provider.Settings.ACTION_ACCESSIBILITY_SETTINGS); 
 +                            try { 
 +                                startActivityForResult(intent, 0); 
 +                            } catch (Exception e) { 
 +································// Accessibility settings cannot be opened 
 +                                reportAccessibilityUnavailable(); 
 +                            } 
 +                        } 
 +                    }) 
 +                    .setCancelable(false) 
 +                    .create() 
 +                    .show(); 
 +        } else { 
 +            configureAndConnect(); 
 +        } 
 +    } 
 + 
 +    private void reportAccessibilityUnavailable() { 
 +        new AlertDialog.Builder(this) 
 +                .setMessage(R.string.accessibility_unavailable_error) 
 +                .setPositiveButton(R.string.button_exit, new DialogInterface.OnClickListener() { 
 +                    @Override 
 +                    public void onClick(DialogInterface dialog, int which) { 
 +                        exitApp(); 
 +                    } 
 +                }) 
 +                .setCancelable(false) 
 +                .create() 
 +                .show(); 
 +    } 
 + 
 +    private void configureAndConnect() { 
 +        if (settingsHelper.getString(SettingsHelper.KEY_SERVER_URL) == null) { 
 +············// Not configured yet 
 +           settingsHelper.setString(SettingsHelper.KEY_SERVER_URL, BuildConfig.DEFAULT_SERVER_URL); 
 +           settingsHelper.setString(SettingsHelper.KEY_SECRET, BuildConfig.DEFAULT_SECRET); 
 +           settingsHelper.setBoolean(SettingsHelper.KEY_USE_DEFAULT, !BuildConfig.DEFAULT_SECRET.equals("")); 
 +           Intent intent = new Intent(this, SettingsActivity.class); 
 + ···········startActivityForResult(intent, Const.REQUEST_SETTINGS); 
 +            return; 
 +        } 
 + 
 +        if (needReconnect) { 
 +            // Here we go after changing settings 
 +            needReconnect = false; 
 +            if (sharingEngine.getState() != Const.STATE_DISCONNECTED) { 
 +                sharingEngine.disconnect(MainActivity.this, (success, errorReason) -> connect()); 
 +           } else { 
 +               connect(); 
 + ···········
 +       } else
 +           if (sharingEngine.getState() == Const.STATE_DISCONNECTED && sharingEngine.getErrorReason() == null)
 +               connect(); 
 +            } 
 +        } 
 +    } 
 + 
 +    @Override 
 +    public void onDestroy() { 
 +        try { 
 +            LocalBroadcastManager.getInstance(this).unregisterReceiver(mSharingServiceReceiver); 
 +       } catch (Exception e) { 
 +        } 
 +        super.onDestroy(); 
 +    } 
 + 
 +    @Override 
 +    public void onSaveInstanceState(Bundle savedInstanceState) { 
 +        savedInstanceState.putString(ATTR_SESSION_ID, sessionId); 
 +        savedInstanceState.putString(ATTR_PASSWORD, password); 
 +        savedInstanceState.putString(ATTR_ADMIN_NAME, adminName); 
 +        super.onSaveInstanceState(savedInstanceState); 
 +    } 
 + 
 +    private void restoreInstanceState(Bundle savedInstanceState) { 
 +        sessionId = savedInstanceState.getString(ATTR_SESSION_ID); 
 +        password = savedInstanceState.getString(ATTR_PASSWORD); 
 +        adminName = savedInstanceState.getString(ATTR_ADMIN_NAME); 
 +    } 
 + 
 +    @Override 
 +    public void onBackPressed() { 
 +        Toast.makeText(this, R.string.back_pressed, Toast.LENGTH_LONG).show(); 
 +    } 
 + 
 +    @Override 
 +    public boolean onCreateOptionsMenu(Menu menu) { 
 +        // Inflate the menu; this adds items to the action bar if it is present. 
 +        getMenuInflater().inflate(R.menu.menu_main, menu); 
 +        return true; 
 +    } 
 + 
 +    @Override 
 +    public boolean onOptionsItemSelected(MenuItem item) { 
 +        // Handle action bar item clicks here. The action bar will 
 +        // automatically handle clicks on the Home/Up button, so long 
 +        // as you specify a parent activity in AndroidManifest.xml. 
 + ·······int id = item.getItemId(); 
 + 
 +        if (id == R.id.action_settings) { 
 +            if (adminName != null) { 
 +                Toast.makeText(this, R.string.settings_unavailable, Toast.LENGTH_LONG).show(); 
 +                return true; 
 +            } 
 +            Intent intent = new Intent(this, SettingsActivity.class); 
 +            startActivityForResult(intent, Const.REQUEST_SETTINGS); 
 +            cancelExitOnIdle(); 
 +            return true; 
 +        } else if (id == R.id.action_about) { 
 +            showAbout(); 
 +            return true; 
 +        } 
 + 
 +        return super.onOptionsItemSelected(item); 
 +    } 
 + 
 +    @Override 
 +    public void onActivityResult(int requestCode, int resultCode, Intent data) { 
 +        super.onActivityResult(requestCode, resultCode, data); 
 + 
 +        if (requestCode == Const.REQUEST_SETTINGS) { 
 +            if (resultCode == Const.RESULT_DIRTY) { 
 +                needReconnect = true; 
 +            } else { 
 +                scheduleExitOnIdle(); 
 +            } 
 +        } else if (requestCode == Const.REQUEST_SCREEN_SHARE) { 
 +            if (resultCode != RESULT_OK) { 
 +                Toast.makeText(this, R.string.screen_cast_denied, Toast.LENGTH_LONG).show(); 
 +                adminName = null; 
 +                updateUI(); 
 +                cancelSharingTimeout(); 
 +                scheduleExitOnIdle(); 
 +            } else { 
 +                ScreenSharingHelper.startSharing(this, resultCode, data); 
 +            } 
 +        } 
 +    } 
 + 
 +    private void initUI() { 
 +        imageViewConnStatus = findViewById(R.id.image_conn_status); 
 +        textViewConnStatus = findViewById(R.id.conn_status); 
 +        editTextSessionId = findViewById(R.id.session_id_edit); 
 +        editTextPassword = findViewById(R.id.password_edit); 
 +        textViewComment = findViewById(R.id.comment); 
 +        textViewConnect = findViewById(R.id.reconnect); 
 +        textViewSendLink = findViewById(R.id.send_link); 
 +        textViewExit = findViewById(R.id.disconnect_exit); 
 + 
 +        textViewConnect.setOnClickListener(v -> connect()); 
 + 
 +       textViewSendLink.setOnClickListener(v -> sendLink()); 
 + 
 + ·······textViewExit.setOnClickListener(v -> gracefulExit()); 
 +   } 
 + 
 +   private void gracefulExit()
 +       if (adminName != null)
 +           notifySharingStop(); 
 +            ScreenSharingHelper.stopSharing(MainActivity.this, true)
 +        } 
 +        sharingEngine.disconnect(MainActivity.this, (success, errorReason) -> exitApp()); 
 +       // 5 sec timeout to exit 
 +       handler.postDelayed(() -> exitApp(), 5000); 
 +   } 
 + 
 +   private void exitApp()
 +       Intent intent = new Intent(MainActivity.this, ScreenSharingService.class); 
 +        stopService(intent); 
 +        intent = new Intent(MainActivity.this, GestureDispatchService.class); 
 +        stopService(intent); 
 +        finishAffinity(); 
 +        System.exit(0); 
 +    } 
 + 
 +    private void updateUI() { 
 +        int[] stateLabels = {R.string.state_disconnected, R.string.state_connecting, R.string.state_connected, R.string.state_sharing, R.string.state_disconnecting}; 
 +        int[] stateImages = {R.drawable.ic_disconnected, R.drawable.ic_connecting, R.drawable.ic_connected, R.drawable.ic_sharing, R.drawable.ic_connecting}; 
 + 
 +        int state = sharingEngine.getState(); 
 +        if (state == Const.STATE_CONNECTED && adminName != null) { 
 +            imageViewConnStatus.setImageDrawable(getDrawable(stateImages[Const.STATE_SHARING])); 
 +            textViewConnStatus.setText(stateLabels[Const.STATE_SHARING]); 
 +       } else { 
 +           imageViewConnStatus.setImageDrawable(getDrawable(stateImages[state])); 
 + ···········textViewConnStatus.setText(stateLabels[state]); 
 +        } 
 +        String serverUrl = Utils.prepareDisplayUrl(settingsHelper.getString(SettingsHelper.KEY_SERVER_URL)); 
 + 
 +        textViewSendLink.setVisibility(state == Const.STATE_CONNECTED ? View.VISIBLE : View.INVISIBLE); 
 +       textViewConnect.setVisibility(state == Const.STATE_DISCONNECTED ? View.VISIBLE : View.INVISIBLE); 
 +        switch (state) { 
 +            case Const.STATE_DISCONNECTED: 
 +                editTextSessionId.setText(""); 
 +               editTextPassword.setText(""); 
 +               if (sharingEngine.getErrorReason() != null) { 
 +                    textViewComment.setText(getString(R.string.hint_connection_error, serverUrl)); 
 +                } 
 +                break; 
 +            case Const.STATE_CONNECTING: 
 +                textViewComment.setText(getString(R.string.hint_connecting, serverUrl)); 
 +                break; 
 +            case Const.STATE_DISCONNECTING: 
 +                textViewComment.setText(getString(R.string.hint_disconnecting)); 
 +                break; 
 +            case Const.STATE_CONNECTED: 
 +                editTextSessionId.setText(sessionId); 
 +                editTextPassword.setText(password); 
 +                textViewComment.setText(adminName != null ? 
 +                        getString(R.string.hint_sharing, adminName) : 
 +                        getString(R.string.hint_connected, serverUrl) 
 +                        ); 
 +                break; 
 +        } 
 +    } 
 + 
 +    private void setDefaultSettings() { 
 +        if (settingsHelper.getString(SettingsHelper.KEY_DEVICE_NAME) == null) { 
 +            settingsHelper.setString(SettingsHelper.KEY_DEVICE_NAME, Build.MANUFACTURER + " " + Build.MODEL); 
 +        } 
 +        if (settingsHelper.getInt(SettingsHelper.KEY_BITRATE) == 0) { 
 +            settingsHelper.setInt(SettingsHelper.KEY_BITRATE, Const.DEFAULT_BITRATE); 
 +        } 
 +        if (settingsHelper.getInt(SettingsHelper.KEY_FRAME_RATE) == 0) { 
 +            settingsHelper.setInt(SettingsHelper.KEY_FRAME_RATE, Const.DEFAULT_FRAME_RATE); 
 +        } 
 +        if (settingsHelper.getInt(SettingsHelper.KEY_IDLE_TIMEOUT) == 0) { 
 +            settingsHelper.setInt(SettingsHelper.KEY_IDLE_TIMEOUT, Const.DEFAULT_IDLE_TIMEOUT); 
 +        } 
 +        if (settingsHelper.getInt(SettingsHelper.KEY_PING_TIMEOUT) == 0) { 
 +            settingsHelper.setInt(SettingsHelper.KEY_PING_TIMEOUT, Const.DEFAULT_PING_TIMEOUT); 
 +        } 
 +    } 
 + 
 +    private void sendLink() { 
 +        String url = settingsHelper.getString(SettingsHelper.KEY_SERVER_URL); 
 +        url += "?session=" + sessionId + "&pin=" + password; 
 +        try { 
 +            Intent shareIntent = new Intent(Intent.ACTION_SEND); 
 +            shareIntent.setType("text/plain"); 
 +            shareIntent.putExtra(Intent.EXTRA_SUBJECT, getString(R.string.send_link_subject)); 
 +            String shareMessage= getString(R.string.send_link_message, url, settingsHelper.getString(SettingsHelper.KEY_DEVICE_NAME)); 
 +            shareIntent.putExtra(Intent.EXTRA_TEXT, shareMessage); 
 +            startActivity(Intent.createChooser(shareIntent, getString(R.string.send_link_chooser))); 
 +        } catch(Exception e) { 
 +            e.printStackTrace(); 
 +            Toast.makeText(this, R.string.send_link_failed, Toast.LENGTH_LONG).show(); 
 +        } 
 +    } 
 + 
 +    private void showAbout() { 
 +        ImageView imageView = new ImageView(this); 
 +        imageView.setImageDrawable(getResources().getDrawable(R.mipmap.ic_launcher)); 
 +        new AlertDialog.Builder(this) 
 +                .setTitle(R.string.about_title) 
 +                .setMessage(getString(R.string.about_message, BuildConfig.VERSION_NAME, BuildConfig.VARIANT)) 
 +                .setPositiveButton(R.string.ok, (dialog, which) -> dialog.dismiss()) 
 +                .create() 
 +                .show(); 
 +    } 
 + 
 +    private void connect() { 
 +        if (sessionId == null || password == null) { 
 +            sessionId = Utils.randomString(8, true); 
 +            password = Utils.randomString(4, true); 
 +        } 
 +        sharingEngine.setUsername(settingsHelper.getString(SettingsHelper.KEY_DEVICE_NAME)); 
 +        sharingEngine.connect(this, sessionId, password, (success, errorReason) -> { 
 +            if (!success) { 
 +                if (errorReason != null && errorReason.equals(Const.ERROR_ICE_FAILED)) { 
 +                    errorReason = getString(R.string.connection_error_ice); 
 +                } 
 +                String message = getString(R.string.connection_error, settingsHelper.getString(SettingsHelper.KEY_SERVER_URL), errorReason); 
 +                reportError(message); 
 +                editTextSessionId.setText(null); 
 +                editTextPassword.setText(null); 
 +            } 
 +        }); 
 + 
 +        scheduleExitOnIdle(); 
 +    } 
 + 
 +    private void reportError(final String message) { 
 +//       Toast.makeText(MainActivity.this, message, Toast.LENGTH_LONG).show(); 
 +        final AlertDialog dialog = new AlertDialog.Builder(this) 
 +                .setMessage(message) 
 +                .setNegativeButton(R.string.copy_message, (dialog1, which) -> { 
 +····················ClipboardManager clipboard = (ClipboardManager) getSystemService(CLIPBOARD_SERVICE); 
 +                   ClipData clip = ClipData.newPlainText(Const.LOG_TAG, message); 
 + ···················clipboard.setPrimaryClip(clip); 
 +                   Toast.makeText(MainActivity.this, R.string.message_copied, Toast.LENGTH_LONG).show(); 
 +                   dialog1.dismiss(); 
 +               }) 
 +               .setPositiveButton(R.string.close, (dialog1, which) -> dialog1.dismiss()) 
 +                .create(); 
 +        try { 
 +            dialog.show(); 
 +            handler.postDelayed(() -> { 
 +                try { 
 +                    dialog.dismiss(); 
 +                } catch (Exception e) { 
 +                } 
 +            }, 10000); 
 +        } catch (Exception e) { 
 +        } 
 +    } 
 + 
 +    @Override 
 +    public void onStartSharing(String adminName) { 
 +········// This event is raised when the admin joins the text room 
 +       this.adminName = adminName; 
 +        updateUI(); 
 +        cancelExitOnIdle(); 
 +        scheduleSharingTimeout(); 
 +        ScreenSharingHelper.requestSharing(this); 
 +    } 
 + 
 +    @Override 
 +    public void onStopSharing() { 
 +········// This event is raised when the admin leaves the text room 
 +        notifySharingStop(); 
 +        adminName = null; 
 +        updateUI(); 
 +        cancelSharingTimeout(); 
 +        scheduleExitOnIdle(); 
 +        ScreenSharingHelper.stopSharing(this, false); 
 +    } 
 + 
 +    @Override 
 +    public void onRemoteControlEvent(String event) { 
 +        Intent intent = new Intent(MainActivity.this, GestureDispatchService.class); 
 +        intent.setAction(Const.ACTION_GESTURE); 
 +        intent.putExtra(Const.EXTRA_EVENT, event); 
 +        startService(intent); 
 +    } 
 + 
 +    @Override 
 +    public void onPing() { 
 +        if (adminName != null) { 
 +            cancelSharingTimeout(); 
 +            scheduleSharingTimeout(); 
 +        } 
 +    } 
 + 
 +    @Override 
 +    public void onSharingApiStateChanged(int state) { 
 +        updateUI(); 
 +        if (state == Const.STATE_CONNECTED) { 
 +            String rtpHost = Utils.getRtpUrl(settingsHelper.getString(SettingsHelper.KEY_SERVER_URL)); 
 +            int rtpAudioPort = sharingEngine.getAudioPort(); 
 +            int rtpVideoPort = sharingEngine.getVideoPort(); 
 +            String testDstIp = settingsHelper.getString(SettingsHelper.KEY_TEST_DST_IP); 
 +            if (testDstIp != null && !testDstIp.trim().equals("")) { 
 +                rtpHost = testDstIp; 
 +                rtpVideoPort = Const.TEST_RTP_PORT; 
 +                Toast.makeText(this, "Test mode: sending stream to " + rtpHost + ":" + rtpVideoPort, Toast.LENGTH_LONG).show(); 
 +            } 
 + 
 +            ScreenSharingHelper.configure(this, settingsHelper.getBoolean(SettingsHelper.KEY_TRANSLATE_AUDIO), 
 +                    settingsHelper.getInt(SettingsHelper.KEY_FRAME_RATE), 
 +                    settingsHelper.getInt(SettingsHelper.KEY_BITRATE), 
 +                    rtpHost, 
 +                    rtpAudioPort, 
 +                    rtpVideoPort 
 +                    ); 
 +        } 
 +    } 
 + 
 +    private void scheduleExitOnIdle() { 
 +        int exitOnIdleTimeout = settingsHelper.getInt(SettingsHelper.KEY_IDLE_TIMEOUT); 
 +        if (exitOnIdleTimeout > 0) { 
 +············exitCounter = EXIT_PROMPT_SEC; 
 +           handler.postDelayed(warningOnIdleRunnable, exitOnIdleTimeout * 1000); 
 +            Log.d(Const.LOG_TAG, "Scheduling exit in " + (exitOnIdleTimeout * 1000) + " sec"); 
 +       } 
 +   } 
 + 
 +   private void cancelExitOnIdle()
 +       Log.d(Const.LOG_TAG, "Cancelling scheduled exit"); 
 +        handler.removeCallbacks(warningOnIdleRunnable); 
 +        handler.removeCallbacks(exitRunnable); 
 + ···} 
 + 
 +    private Runnable exitRunnable = () -> { 
 +········exitCounter--; 
 +       if (exitCounter > 0) { 
 +            TextView messageView = exitOnIdleDialog.findViewById(android.R.id.message); 
 +            if (messageView != null) { 
 +                messageView.setText(MainActivity.this.getResources().getString(R.string.app_idle_warning, exitCounter)); 
 +            } 
 +            scheduleExitRunnable(); 
 + 
 +        } else { 
 +            gracefulExit(); 
 +        } 
 +    }; 
 + 
 +    private Runnable warningOnIdleRunnable = () -> { 
 +        exitOnIdleDialog = new AlertDialog.Builder(MainActivity.this) 
 +                .setMessage(MainActivity.this.getResources().getString(R.string.app_idle_warning, exitCounter)) 
 +                .setPositiveButton(R.string.button_exit, (dialog1, which) -> { 
 +                    gracefulExit(); 
 +                }) 
 +                .setNegativeButton(R.string.button_keep_idle, (dialog1, which) -> { 
 +                    scheduleExitOnIdle(); 
 +                    handler.removeCallbacks(exitRunnable); 
 +                    dialog1.dismiss(); 
 +                }) 
 +                .setCancelable(false) 
 +                .create(); 
 +        try { 
 +            exitOnIdleDialog.show(); 
 +            scheduleExitRunnable(); 
 +        } catch (Exception e) { 
 +            gracefulExit(); 
 +        } 
 +    }; 
 + 
 +    private void scheduleExitRunnable() { 
 +        handler.postDelayed(exitRunnable, 1000); 
 +    } 
 + 
 +    private void scheduleSharingTimeout() { 
 +        int pingTimeout = settingsHelper.getInt(SettingsHelper.KEY_PING_TIMEOUT); 
 +        if (pingTimeout > 0) { 
 +            Log.d(Const.LOG_TAG, "Scheduling sharing stop in " + (pingTimeout * 1000) + " sec"); 
 +           handler.postDelayed(sharingStopByPingTimeoutRunnable, pingTimeout * 1000); 
 +       } 
 +   } 
 + 
 +   private void cancelSharingTimeout()
 +       Log.d(Const.LOG_TAG, "Cancelling scheduled sharing stop"); 
 +        handler.removeCallbacks(sharingStopByPingTimeoutRunnable); 
 +    } 
 + 
 +    private Runnable sharingStopByPingTimeoutRunnable = new Runnable() { 
 +        @Override 
 +        public void run() { 
 +            Toast.makeText(MainActivity.this, R.string.app_sharing_session_ping_timeout, Toast.LENGTH_LONG).show(); 
 +            if (adminName != null) { 
 +                notifySharingStop(); 
 +                ScreenSharingHelper.stopSharing(MainActivity.this, false); 
 +            } 
 +            adminName = null; 
 +            updateUI(); 
 +            cancelSharingTimeout(); 
 +            scheduleExitOnIdle(); 
 +            sharingEngine.disconnect(MainActivity.this, (success, errorReason) -> connect()); 
 +        } 
 +    }; 
 + 
 +    private Runnable overlayDotRunnable = new Runnable() { 
 +        @Override 
 +        public void run() { 
 +            if (overlayDotDirection == 0) { 
 +                return; 
 +            } 
 +            overlayDotAlpha += OVERLAY_DOT_ANIMATION_INCREMENT * overlayDotDirection; 
 +            if (overlayDotAlpha > 255) { 
 +                overlayDotAlpha = 255; 
 +                overlayDotDirection = -overlayDotDirection; 
 +            } 
 +            if (overlayDotAlpha < 128) { 
 +                overlayDotAlpha = 128; 
 +                overlayDotDirection = -overlayDotDirection; 
 +            } 
 +            overlayDot.setImageAlpha(overlayDotAlpha); 
 +            handler.postDelayed(overlayDotRunnable, OVERLAY_DOT_ANIMATION_DELAY); 
 +        } 
 +    }; 
 + 
 +    private void notifySharingStart() { 
 +        notifyGestureService(Const.ACTION_SCREEN_SHARING_START); 
 +        if (settingsHelper.getBoolean(SettingsHelper.KEY_NOTIFY_SHARING)) { 
 +            // Show a flashing dot 
 +           Utils.lockDeviceRotation(this, true); 
 +            overlayDot = createOverlayDot(); 
 +            overlayDotAlpha = 0; 
 +            overlayDotDirection = 1; 
 +            handler.postDelayed(overlayDotRunnable, OVERLAY_DOT_ANIMATION_DELAY); 
 + 
 +        } else { 
 +            // Just show some dialog to trigger the traffic 
 +            final AlertDialog dialog = new AlertDialog.Builder(MainActivity.this) 
 +                   .setMessage(R.string.share_start_text) 
 +····················.setPositiveButton(R.string.ok, (dialog1, which) -> dialog1.dismiss()) 
 +                    .create(); 
 +            dialog.show(); 
 +            handler.postDelayed(() -> { 
 +                if (dialog != null && dialog.isShowing()) { 
 +                    try { 
 +                        dialog.dismiss(); 
 +                    } catch (Exception e) { 
 +                    } 
 +                } 
 +            }, 3000); 
 +        } 
 +    } 
 + 
 +    private void notifySharingStop() { 
 +        notifyGestureService(Const.ACTION_SCREEN_SHARING_STOP); 
 +        if (settingsHelper.getBoolean(SettingsHelper.KEY_NOTIFY_SHARING)) { 
 +            overlayDotDirection = 0; 
 +            if (overlayDot != null) { 
 +                WindowManager wm = (WindowManager) getSystemService(Context.WINDOW_SERVICE); 
 +                wm.removeView(overlayDot); 
 +                overlayDot = null; 
 +            } 
 +            Utils.lockDeviceRotation(this, false); 
 +        } 
 +    } 
 + 
 +    private void notifyGestureService(String action) { 
 +        Intent intent = new Intent(MainActivity.this, GestureDispatchService.class); 
 +        intent.setAction(action); 
 +        startService(intent); 
 +    } 
 + 
 +    public ImageView createOverlayDot() { 
 +        int size = getResources().getDimensionPixelOffset(R.dimen.overlay_dot_size); 
 +        WindowManager.LayoutParams params = new WindowManager.LayoutParams(size, size, 
 +                Utils.OverlayWindowType(), 
 +                WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE 
 +                        |WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL 
 +                        |WindowManager.LayoutParams.FLAG_WATCH_OUTSIDE_TOUCH, 
 +                PixelFormat.TRANSLUCENT); 
 +        params.gravity = Gravity.LEFT | Gravity.TOP; 
 +        params.x = getResources().getDimensionPixelOffset(R.dimen.overlay_dot_offset); 
 +        params.y = getResources().getDimensionPixelOffset(R.dimen.overlay_dot_offset); 
 + 
 +        ImageView view = new ImageView(this); 
 +        view.setImageResource(R.drawable.flash_dot); 
 +        view.setImageAlpha(0); 
 +        WindowManager wm = (WindowManager)getSystemService(Context.WINDOW_SERVICE); 
 +        wm.addView(view, params); 
 +        return view; 
 +    } 
 +}
-[[history_old|Older versions]] 
-~~NOTOC~~ 
-~~ARCHIVE=history_old~~ 

Last modified: by 188.43.235.177